[input] widgets must say if they are interested in click or drags

This commit is contained in:
Emil Ernerfeldt 2020-05-23 12:38:52 +02:00
parent 4bea65595c
commit 5c966bdc76
14 changed files with 170 additions and 114 deletions

View file

@ -163,7 +163,8 @@ impl Prepared {
} else {
None
};
let move_interact = ctx.interact(layer, clip_rect, rect, interact_id);
let move_interact =
ctx.interact(layer, clip_rect, rect, interact_id, Sense::click_and_drag());
let input = ctx.input();
if move_interact.active {

View file

@ -96,7 +96,7 @@ impl State {
&mut self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<(R, InteractInfo)> {
) -> Option<(R, Rect)> {
let openness = self.openness(ui);
let animate = 0.0 < openness && openness < 1.0;
if animate {
@ -132,7 +132,7 @@ impl State {
}))
} else if self.open {
let r_interact = ui.add_custom(add_contents);
let full_size = r_interact.1.rect.size();
let full_size = r_interact.1.size();
self.open_height = Some(full_size.y);
Some(r_interact)
} else {
@ -190,13 +190,13 @@ impl CollapsingHeader {
let desired_width = text_max_x - available.left();
let desired_width = desired_width.max(available.width());
let interact = ui.reserve_space(
vec2(
desired_width,
galley.size.y + 2.0 * ui.style().button_padding.y,
),
Some(id),
let size = vec2(
desired_width,
galley.size.y + 2.0 * ui.style().button_padding.y,
);
let rect = ui.allocate_space(size);
let interact = ui.interact(rect, id, Sense::click());
let text_pos = pos2(text_pos.x, interact.rect.center().y - galley.size.y / 2.0);
let mut state = State::from_memory_with_default_open(ui, id, default_open);

View file

@ -20,7 +20,7 @@ impl Default for BarState {
}
}
pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, InteractInfo) {
pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) {
ui.inner_layout(Layout::horizontal(Align::Center), |ui| {
Frame::menu_bar(ui.style()).show(ui, |ui| {
let mut style = ui.style().clone();

View file

@ -209,7 +209,7 @@ impl Resize {
position + state.size + self.handle_offset - corner_size,
corner_size,
);
let corner_interact = ui.interact_rect(corner_rect, id.with("corner"));
let corner_interact = ui.interact(corner_rect, id.with("corner"), Sense::drag());
if corner_interact.active {
if let Some(mouse_pos) = ui.input().mouse.pos {
@ -304,7 +304,7 @@ impl Resize {
// state.size = state.size.clamp(self.min_size..=self.max_size);
state.size = state.size.round(); // TODO: round to pixels
ui.reserve_space(state.size, None);
ui.allocate_space(state.size);
// ------------------------------

View file

@ -146,8 +146,8 @@ impl Prepared {
let content_is_too_small = content_size.y > inner_rect.height();
if content_is_too_small {
// Dragg contents to scroll (for touch screens mostly):
let content_interact = ui.interact_rect(inner_rect, id.with("area"));
// Drag contents to scroll (for touch screens mostly):
let content_interact = ui.interact(inner_rect, id.with("area"), Sense::drag());
if content_interact.active {
state.offset.y -= ui.input().mouse.delta.y;
}
@ -181,7 +181,7 @@ impl Prepared {
// intentionally use same id for inside and outside of handle
let interact_id = id.with("vertical");
let handle_interact = ui.interact_rect(handle_rect, interact_id);
let handle_interact = ui.interact(handle_rect, interact_id, Sense::click_and_drag());
if let Some(mouse_pos) = ui.input().mouse.pos {
if handle_interact.active {
@ -191,7 +191,8 @@ impl Prepared {
}
} else {
// Check for mouse down outside handle:
let scroll_bg_interact = ui.interact_rect(outer_scroll_rect, interact_id);
let scroll_bg_interact =
ui.interact(outer_scroll_rect, interact_id, Sense::click_and_drag());
if scroll_bg_interact.active {
// Center scroll at mouse pos:
@ -235,7 +236,7 @@ impl Prepared {
// content_size.y.min(inner_rect.size().y), // respect vertical height.
// );
let size = outer_rect.size();
ui.reserve_space(size, None);
ui.allocate_space(size);
state.offset.y = state.offset.y.min(content_size.y - inner_rect.height());
state.offset.y = state.offset.y.max(0.0);

View file

@ -529,10 +529,10 @@ fn show_title_bar(
{
// TODO: make clickable radius larger
ui.reserve_space(vec2(0.0, 0.0), None); // HACK: will add left spacing
ui.allocate_space(vec2(0.0, 0.0)); // HACK: will add left spacing
let collapse_button_interact =
ui.reserve_space(Vec2::splat(button_size), Some(collapsing_id));
let rect = ui.allocate_space(Vec2::splat(button_size));
let collapse_button_interact = ui.interact(rect, collapsing_id, Sense::click());
if collapse_button_interact.clicked {
// TODO: also do this when double-clicking window title
collapsing.toggle(ui);
@ -541,7 +541,7 @@ fn show_title_bar(
}
let title_galley = title_label.layout(ui);
let title_rect = ui.reserve_space(title_galley.size, None).rect;
let title_rect = ui.allocate_space(title_galley.size);
if show_close_button {
// Reserve space for close button which will be added later:
@ -566,7 +566,7 @@ fn show_title_bar(
});
TitleBar {
rect: tb_interact.1.rect,
rect: tb_interact.1,
..tb_interact.0
}
}
@ -577,8 +577,8 @@ impl TitleBar {
.paint_galley(ui, self.title_rect.min, self.title_galley);
}
pub fn close_button_ui(&self, ui: &mut Ui, content: &Option<InteractInfo>) -> InteractInfo {
let right = content.map(|c| c.rect.right()).unwrap_or(self.rect.right());
pub fn close_button_ui(&self, ui: &mut Ui, content: &Option<Rect>) -> InteractInfo {
let right = content.map(|c| c.right()).unwrap_or(self.rect.right());
let button_size = ui.style().start_icon_width;
let button_rect = Rect::from_min_size(
@ -595,7 +595,7 @@ impl TitleBar {
fn close_button(ui: &mut Ui, rect: Rect) -> InteractInfo {
let close_id = ui.make_child_id("window_close_button");
let interact = ui.interact_rect(rect, close_id);
let interact = ui.interact(rect, close_id, Sense::click());
ui.expand_to_include_child(interact.rect);
let stroke_color = ui.style().interact(&interact).stroke_color;

View file

@ -148,16 +148,19 @@ impl Context {
fn begin_frame_mut(&mut self, new_raw_input: RawInput) {
if !self.input.mouse.down || self.input.mouse.pos.is_none() {
// mouse was not down last frame
self.memory().active_id = None;
let window_interaction = self.memory().window_interaction.take();
if let Some(window_interaction) = window_interaction {
let area_layer = window_interaction.area_layer;
let area_state = self.memory().areas.get(area_layer.id).clone();
if let Some(mut area_state) = area_state {
if !window_interaction.is_resize() {
// Throw windows because it is fun:
area_state.vel = self.input().mouse.velocity;
self.memory().areas.set_state(area_layer, area_state);
let area_layer = window_interaction.area_layer;
let area_state = self.memory().areas.get(area_layer.id).clone();
if let Some(mut area_state) = area_state {
area_state.vel = self.input().mouse.velocity;
self.memory().areas.set_state(area_layer, area_state);
}
}
}
}
@ -287,15 +290,38 @@ impl Context {
clip_rect: Rect,
rect: Rect,
interaction_id: Option<Id>,
sense: Sense,
) -> InteractInfo {
let interact_rect = rect.expand2(0.5 * self.style().item_spacing); // make it easier to click. TODO: nice way to do this
let hovered = self.contains_mouse(layer, clip_rect, interact_rect);
if interaction_id.is_none() || sense == Sense::nothing() {
// Not interested in input:
return InteractInfo {
rect,
hovered,
clicked: false,
active: false,
};
}
let interaction_id = interaction_id.unwrap();
let mut memory = self.memory();
let active = interaction_id.is_some() && memory.active_id == interaction_id;
let active = memory.active_id == Some(interaction_id);
if active && !sense.drag && !self.input().mouse.could_be_click {
// Aborted click
memory.active_id = None;
return InteractInfo {
rect,
hovered: false,
clicked: false,
active: false,
};
}
if self.input.mouse.pressed {
if hovered && interaction_id.is_some() {
if hovered {
if memory.active_id.is_some() {
// Already clicked something else this frame
InteractInfo {
@ -305,7 +331,8 @@ impl Context {
active: false,
}
} else {
memory.active_id = interaction_id;
// start of a click or drag
memory.active_id = Some(interaction_id);
InteractInfo {
rect,
hovered,
@ -314,6 +341,7 @@ impl Context {
}
}
} else {
// miss
InteractInfo {
rect,
hovered,
@ -325,7 +353,7 @@ impl Context {
InteractInfo {
rect,
hovered,
clicked: hovered && active && self.input().mouse.could_be_click,
clicked: hovered && active,
active,
}
} else if self.input.mouse.down {

View file

@ -394,11 +394,7 @@ impl BoxPainting {
ui.add(Slider::usize(&mut self.num_boxes, 0..=5).text("num_boxes"));
let pos = ui
.reserve_space(
vec2(self.size.x * (self.num_boxes as f32), self.size.y),
None,
)
.rect
.allocate_space(vec2(self.size.x * (self.num_boxes as f32), self.size.y))
.min;
let mut cmds = vec![];
@ -438,7 +434,8 @@ impl Painting {
}
fn content(&mut self, ui: &mut Ui) {
let interact = ui.reserve_space(ui.available_finite().size(), Some(ui.id()));
let rect = ui.allocate_space(ui.available_finite().size());
let interact = ui.interact(rect, ui.id(), Sense::drag());
let rect = interact.rect;
ui.set_clip_rect(ui.clip_rect().intersect(rect)); // Make sure we don't paint out of bounds

View file

@ -17,8 +17,7 @@ impl Texture {
if size.x > ui.available().width() {
size *= ui.available().width() / size.x;
}
let interact = ui.reserve_space(size, None);
let rect = interact.rect;
let rect = ui.allocate_space(size);
let top_left = Vertex {
pos: rect.min,
uv: (0, 0),
@ -33,10 +32,10 @@ impl Texture {
triangles.add_rect(top_left, bottom_right);
ui.add_paint_cmd(PaintCmd::Triangles(triangles));
if interact.hovered {
if ui.hovered(rect) {
show_tooltip(ui.ctx(), |ui| {
let pos = ui.top_left();
let zoom_rect = ui.reserve_space(vec2(128.0, 128.0), None).rect;
let zoom_rect = ui.allocate_space(vec2(128.0, 128.0));
let u = remap_clamp(pos.x, rect.range_x(), 0.0..=self.width as f32 - 1.0).round();
let v = remap_clamp(pos.y, rect.range_y(), 0.0..=self.height as f32 - 1.0).round();

View file

@ -111,3 +111,44 @@ impl GuiResponse {
}
// ----------------------------------------------------------------------------
/// What sort of interaction is a widget sensitive to?
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Sense {
/// buttons, sliders, windows ...
pub click: bool,
/// sliders, windows, scroll bars, scroll areas ...
pub drag: bool,
}
impl Sense {
pub fn nothing() -> Self {
Self {
click: false,
drag: false,
}
}
pub fn click() -> Self {
Self {
click: true,
drag: false,
}
}
pub fn drag() -> Self {
Self {
click: false,
drag: true,
}
}
/// e.g. a slider or window
pub fn click_and_drag() -> Self {
Self {
click: true,
drag: true,
}
}
}

View file

@ -303,14 +303,18 @@ impl Ui {
// ------------------------------------------------------------------------
// Interaction
/// Check for clicks on this entire ui (rect())
pub fn interact_whole(&self) -> InteractInfo {
self.interact_rect(self.rect(), self.id)
pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> InteractInfo {
self.ctx
.interact(self.layer, self.clip_rect, rect, Some(id), sense)
}
pub fn interact_rect(&self, rect: Rect, id: Id) -> InteractInfo {
pub fn interact_hover(&self, rect: Rect) -> InteractInfo {
self.ctx
.interact(self.layer, self.clip_rect, rect, Some(id))
.interact(self.layer, self.clip_rect, rect, None, Sense::nothing())
}
pub fn hovered(&self, rect: Rect) -> bool {
self.interact_hover(rect).hovered
}
#[must_use]
@ -340,15 +344,6 @@ impl Ui {
/// for `Justified` aligned layouts, like in menus.
///
/// You may get LESS space than you asked for if the current layout won't fit what you asked for.
///
/// TODO: remove, or redesign or something and start using allocate_space
pub fn reserve_space(&mut self, child_size: Vec2, interaction_id: Option<Id>) -> InteractInfo {
let rect = self.allocate_space(child_size);
self.ctx
.interact(self.layer, self.clip_rect, rect, interaction_id)
}
pub fn allocate_space(&mut self, child_size: Vec2) -> Rect {
let child_size = self.round_vec_to_pixels(child_size);
self.cursor = self.round_pos_to_pixels(self.cursor);
@ -530,25 +525,21 @@ impl Ui {
/// Just because you ask for a lot of space does not mean you have to use it!
/// After `add_contents` is called the contents of `bounding_size`
/// will decide how much space will be used in the parent ui.
pub fn add_custom_contents(
&mut self,
size: Vec2,
add_contents: impl FnOnce(&mut Ui),
) -> InteractInfo {
pub fn add_custom_contents(&mut self, size: Vec2, add_contents: impl FnOnce(&mut Ui)) -> Rect {
let size = size.min(self.available().size());
let child_rect = Rect::from_min_size(self.cursor, size);
let mut child_ui = self.child_ui(child_rect);
add_contents(&mut child_ui);
self.reserve_space(child_ui.bounding_size(), None)
self.allocate_space(child_ui.bounding_size())
}
/// Create a child ui
pub fn add_custom<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, InteractInfo) {
pub fn add_custom<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) {
let child_rect = self.available();
let mut child_ui = self.child_ui(child_rect);
let r = add_contents(&mut child_ui);
let size = child_ui.bounding_size();
(r, self.reserve_space(size, None))
(r, self.allocate_space(size))
}
/// Create a child ui which is indented to the right
@ -556,7 +547,7 @@ impl Ui {
&mut self,
id_source: impl Hash,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> (R, InteractInfo) {
) -> (R, Rect) {
assert!(
self.layout().dir() == Direction::Vertical,
"You can only indent vertical layouts"
@ -580,7 +571,7 @@ impl Ui {
self.style.line_width,
));
(ret, self.reserve_space(indent + size, None))
(ret, self.allocate_space(indent + size))
}
pub fn left_column(&mut self, width: f32) -> Ui {
@ -609,12 +600,12 @@ impl Ui {
}
/// Start a ui with horizontal layout
pub fn horizontal<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, InteractInfo) {
pub fn horizontal<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) {
self.inner_layout(Layout::horizontal(Align::Min), add_contents)
}
/// Start a ui with vertical layout
pub fn vertical<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, InteractInfo) {
pub fn vertical<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) {
self.inner_layout(Layout::vertical(Align::Min), add_contents)
}
@ -622,7 +613,7 @@ impl Ui {
&mut self,
layout: Layout,
add_contents: impl FnOnce(&mut Self) -> R,
) -> (R, InteractInfo) {
) -> (R, Rect) {
let child_rect = Rect::from_min_max(self.cursor, self.bottom_right());
let mut child_ui = Self {
..self.child_ui(child_rect)
@ -630,8 +621,8 @@ impl Ui {
child_ui.set_layout(layout); // HACK: need a separate call right now
let ret = add_contents(&mut child_ui);
let size = child_ui.bounding_size();
let interact = self.reserve_space(size, None);
(ret, interact)
let rect = self.allocate_space(size);
(ret, rect)
}
/// Temporarily split split an Ui into several columns.
@ -678,7 +669,7 @@ impl Ui {
}
let size = vec2(self.available().width().max(sum_width), max_height);
self.reserve_space(size, None);
self.allocate_space(size);
result
}

View file

@ -108,9 +108,9 @@ macro_rules! label {
impl Widget for Label {
fn ui(self, ui: &mut Ui) -> GuiResponse {
let galley = self.layout(ui);
let interact = ui.reserve_space(galley.size, None);
self.paint_galley(ui, interact.rect.min, galley);
ui.response(interact)
let rect = ui.allocate_space(galley.size);
self.paint_galley(ui, rect.min, galley);
ui.response(ui.interact_hover(rect))
}
}
@ -158,7 +158,8 @@ impl Widget for Hyperlink {
let id = ui.make_child_id(&url);
let font = &ui.fonts()[text_style];
let galley = font.layout_multiline(text, ui.available().width());
let interact = ui.reserve_space(galley.size, Some(id));
let rect = ui.allocate_space(galley.size);
let interact = ui.interact(rect, id, Sense::click());
if interact.hovered {
ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
}
@ -239,7 +240,8 @@ impl Widget for Button {
let padding = ui.style().button_padding;
let mut size = galley.size + 2.0 * padding;
size.y = size.y.max(ui.style().clickable_diameter);
let interact = ui.reserve_space(size, Some(id));
let rect = ui.allocate_space(size);
let interact = ui.interact(rect, id, Sense::click());
let text_cursor = interact.rect.left_center() + vec2(padding.x, -0.5 * galley.size.y);
let bg_fill_color = fill_color.or(ui.style().interact(&interact).bg_fill_color);
ui.add_paint_cmd(PaintCmd::Rect {
@ -291,13 +293,12 @@ impl<'a> Widget for Checkbox<'a> {
let text_style = TextStyle::Button;
let font = &ui.fonts()[text_style];
let galley = font.layout_single_line(text);
let interact = ui.reserve_space(
ui.style().button_padding
+ vec2(ui.style().start_icon_width, 0.0)
+ galley.size
+ ui.style().button_padding,
Some(id),
);
let size = ui.style().button_padding
+ vec2(ui.style().start_icon_width, 0.0)
+ galley.size
+ ui.style().button_padding;
let rect = ui.allocate_space(size);
let interact = ui.interact(rect, id, Sense::click());
let text_cursor =
interact.rect.min + ui.style().button_padding + vec2(ui.style().start_icon_width, 0.0);
if interact.clicked {
@ -370,13 +371,12 @@ impl Widget for RadioButton {
let text_style = TextStyle::Button;
let font = &ui.fonts()[text_style];
let galley = font.layout_multiline(text, ui.available().width());
let interact = ui.reserve_space(
ui.style().button_padding
+ vec2(ui.style().start_icon_width, 0.0)
+ galley.size
+ ui.style().button_padding,
Some(id),
);
let size = ui.style().button_padding
+ vec2(ui.style().start_icon_width, 0.0)
+ galley.size
+ ui.style().button_padding;
let rect = ui.allocate_space(size);
let interact = ui.interact(rect, id, Sense::click());
let text_cursor =
interact.rect.min + ui.style().button_padding + vec2(ui.style().start_icon_width, 0.0);
@ -461,27 +461,25 @@ impl Widget for Separator {
let available_space = ui.available_finite().size();
let (points, interact) = match ui.layout().dir() {
let (points, rect) = match ui.layout().dir() {
Direction::Horizontal => {
let interact = ui.reserve_space(vec2(min_spacing, available_space.y), None);
let r = &interact.rect;
let rect = ui.allocate_space(vec2(min_spacing, available_space.y));
(
[
pos2(r.center().x, r.top() - extra),
pos2(r.center().x, r.bottom() + extra),
pos2(rect.center().x, rect.top() - extra),
pos2(rect.center().x, rect.bottom() + extra),
],
interact,
rect,
)
}
Direction::Vertical => {
let interact = ui.reserve_space(vec2(available_space.x, min_spacing), None);
let r = &interact.rect;
let rect = ui.allocate_space(vec2(available_space.x, min_spacing));
(
[
pos2(r.left() - extra, r.center().y),
pos2(r.right() + extra, r.center().y),
pos2(rect.left() - extra, rect.center().y),
pos2(rect.right() + extra, rect.center().y),
],
interact,
rect,
)
}
};
@ -490,6 +488,6 @@ impl Widget for Separator {
color: color,
width: line_width,
});
ui.response(interact)
ui.response(ui.interact_hover(rect))
}
}

View file

@ -117,7 +117,7 @@ impl<'a> Widget for Slider<'a> {
if text_on_top {
let galley = font.layout_single_line(full_text);
let pos = ui.reserve_space(galley.size, None).rect.min;
let pos = ui.allocate_space(galley.size).min;
ui.add_galley(pos, galley, text_style, text_color);
slider_sans_text.ui(ui)
} else {
@ -140,13 +140,12 @@ impl<'a> Widget for Slider<'a> {
let id = self.id.unwrap_or_else(|| ui.make_position_id());
let interact = ui.reserve_space(
Vec2 {
x: ui.available().width(),
y: height,
},
Some(id),
);
let size = Vec2 {
x: ui.available().width(),
y: height,
};
let rect = ui.allocate_space(size);
let interact = ui.interact(rect, id, Sense::click_and_drag());
let left = interact.rect.left() + handle_radius;
let right = interact.rect.right() - handle_radius;

View file

@ -71,7 +71,8 @@ impl<'t> Widget for TextEdit<'t> {
font.layout_single_line(text.clone())
};
let desired_size = galley.size.max(vec2(available_width, line_spacing));
let interact = ui.reserve_space(desired_size, Some(id));
let rect = ui.allocate_space(desired_size);
let interact = ui.interact(rect, id, Sense::click_and_drag()); // TODO: implement drag-select
if interact.clicked {
ui.request_kb_focus(id);