diff --git a/emigui/src/containers/window.rs b/emigui/src/containers/window.rs index 90f4a421..e9764f4a 100644 --- a/emigui/src/containers/window.rs +++ b/emigui/src/containers/window.rs @@ -168,51 +168,54 @@ impl<'open> Window<'open> { let mut area = area.begin(ctx); { + // BEGIN FRAME -------------------------------- let mut frame = frame.begin(&mut area.content_ui); - let content_rect; - let title_bar; - { - let ui = &mut frame.content_ui; + let default_expanded = true; + let mut collapsing = collapsing_header::State::from_memory_with_default_open( + &mut frame.content_ui, + collapsing_id, + default_expanded, + ); + let show_close_button = open.is_some(); + let title_bar = show_title_bar( + &mut frame.content_ui, + title_label, + show_close_button, + collapsing_id, + &mut collapsing, + ); - let default_expanded = true; - let mut collapsing = collapsing_header::State::from_memory_with_default_open( - ui, - collapsing_id, - default_expanded, - ); - let show_close_button = open.is_some(); - title_bar = show_title_bar( - ui, - title_label, - show_close_button, - collapsing_id, - &mut collapsing, - ); + let content_rect = collapsing + .add_contents(&mut frame.content_ui, |ui| { + resize.show(ui, |ui| { + // Add some spacing (item_spacing) between title and content: + ui.allocate_space(Vec2::zero()); - content_rect = collapsing - .add_contents(ui, |ui| { - resize.show(ui, |ui| { - // Add some spacing (item_spacing) between title and content: - ui.allocate_space(Vec2::zero()); - - if let Some(scroll) = scroll { - scroll.show(ui, add_contents) - } else { - add_contents(ui) - } - }) + if let Some(scroll) = scroll { + scroll.show(ui, add_contents) + } else { + add_contents(ui) + } }) - .map(|ri| ri.1); - - ui.memory() - .collapsing_headers - .insert(collapsing_id, collapsing); - } + }) + .map(|ri| ri.1); let outer_rect = frame.end(&mut area.content_ui); + // END FRAME -------------------------------- - title_bar.ui(&mut area.content_ui, outer_rect, content_rect, open); + title_bar.ui( + &mut area.content_ui, + outer_rect, + content_rect, + open, + &mut collapsing, + ); + + area.content_ui + .memory() + .collapsing_headers + .insert(collapsing_id, collapsing); let interaction = if possible.movable || possible.resizable { interact( @@ -524,7 +527,7 @@ fn show_title_bar( collapsing_id: Id, collapsing: &mut collapsing_header::State, ) -> TitleBar { - let tb_interact = ui.inner_layout(Layout::horizontal(Align::Center), |ui| { + let title_bar_and_rect = ui.inner_layout(Layout::horizontal(Align::Center), |ui| { ui.set_desired_height(title_label.font_height(ui)); let item_spacing = ui.style().item_spacing; @@ -537,7 +540,6 @@ fn show_title_bar( 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); } collapsing.paint_icon(ui, &collapse_button_interact); @@ -569,28 +571,35 @@ fn show_title_bar( }); TitleBar { - rect: tb_interact.1, - ..tb_interact.0 + rect: title_bar_and_rect.1, + ..title_bar_and_rect.0 } } impl TitleBar { fn ui( - self, + mut self, ui: &mut Ui, outer_rect: Rect, content_rect: Option, open: Option<&mut bool>, + collapsing: &mut collapsing_header::State, ) { + if let Some(content_rect) = content_rect { + // Now we know how large we got to be: + self.rect.max.x = content_rect.max.x; + } + if let Some(open) = open { // Add close button now that we know our full width: - if self.close_button_ui(ui, &content_rect).clicked { + if self.close_button_ui(ui).clicked { *open = false; } } // TODO: pick style for title based on move interaction - self.title_ui(ui); + self.title_label + .paint_galley(ui, self.title_rect.min, self.title_galley); if let Some(content_rect) = content_rect { // paint separator between title and content: @@ -602,20 +611,21 @@ impl TitleBar { style: ui.style().interact.inactive.rect_outline.unwrap(), }); } + + let title_bar_id = ui.make_child_id("title_bar"); + if ui + .interact(self.rect, title_bar_id, Sense::click()) + .double_clicked + { + collapsing.toggle(ui); + } } - fn title_ui(self, ui: &mut Ui) { - self.title_label - .paint_galley(ui, self.title_rect.min, self.title_galley); - } - - fn close_button_ui(&self, ui: &mut Ui, content_rect: &Option) -> InteractInfo { - let right = content_rect.map(|c| c.right()).unwrap_or(self.rect.right()); - + fn close_button_ui(&self, ui: &mut Ui) -> InteractInfo { let button_size = ui.style().start_icon_width; let button_rect = Rect::from_min_size( pos2( - right - ui.style().item_spacing.x - button_size, + self.rect.right() - ui.style().item_spacing.x - button_size, self.rect.center().y - 0.5 * button_size, ), Vec2::splat(button_size), diff --git a/emigui/src/context.rs b/emigui/src/context.rs index 83055b7f..93551eb5 100644 --- a/emigui/src/context.rs +++ b/emigui/src/context.rs @@ -36,7 +36,6 @@ pub struct Context { paint_stats: Mutex, } -// TODO: remove this impl. impl Clone for Context { fn clone(&self) -> Self { Context { @@ -280,6 +279,7 @@ impl Context { rect, hovered, clicked: false, + double_clicked: false, active: false, }; } @@ -299,6 +299,7 @@ impl Context { rect, hovered: true, clicked: false, + double_clicked: false, active: false, }; @@ -321,14 +322,17 @@ impl Context { rect, hovered, clicked: false, + double_clicked: false, active: false, } } } else if self.input.mouse.released { + let clicked = hovered && active; InteractInfo { rect, hovered, - clicked: hovered && active, + clicked, + double_clicked: clicked && self.input.mouse.double_click, active, } } else if self.input.mouse.down { @@ -336,6 +340,7 @@ impl Context { rect, hovered: hovered && active, clicked: false, + double_clicked: false, active, } } else { @@ -343,6 +348,7 @@ impl Context { rect, hovered, clicked: false, + double_clicked: false, active, } } diff --git a/emigui/src/input.rs b/emigui/src/input.rs index 195809c1..3669a9f0 100644 --- a/emigui/src/input.rs +++ b/emigui/src/input.rs @@ -2,6 +2,11 @@ use serde_derive::Deserialize; use crate::{math::*, movement_tracker::MovementTracker}; +/// If mouse moves more than this, it is no longer a click (but maybe a drag) +const MAX_CLICK_DIST: f32 = 6.0; +/// The new mouse press must come within this many seconds from previous mouse release +const MAX_CLICK_DELAY: f64 = 0.3; + /// What the integration gives to the gui. /// All coordinates in emigui is in point/logical coordinates. #[derive(Clone, Debug, Default, Deserialize)] @@ -77,6 +82,21 @@ pub struct MouseInput { /// The mouse went from down to !down pub released: bool, + /// If the mouse is down, will it register as a click when released? + /// Set to true on mouse down, set to false when mouse moves too much. + pub could_be_click: bool, + + /// Was there a click? + /// Did a mouse button get released this frame closely after going down? + pub click: bool, + + /// Was there a double-click? + pub double_click: bool, + + /// When did the mouse get click last? + /// Used to check for double-clicks. + pub last_click_time: f64, + /// Current position of the mouse in points. /// None for touch screens when finger is not down. pub pos: Option, @@ -84,10 +104,6 @@ pub struct MouseInput { /// Where did the current click/drag originate? pub press_origin: Option, - /// If the mouse is down, will it register as a click when released? - /// Set to true on mouse down, set to false when mouse moves too much. - pub could_be_click: bool, - /// How much the mouse moved compared to last frame, in points. pub delta: Vec2, @@ -105,9 +121,12 @@ impl Default for MouseInput { down: false, pressed: false, released: false, + could_be_click: false, + click: false, + double_click: false, + last_click_time: std::f64::NEG_INFINITY, pos: None, press_origin: None, - could_be_click: false, delta: Vec2::zero(), velocity: Vec2::zero(), pos_tracker: MovementTracker::new(1000, 0.1), @@ -181,8 +200,15 @@ impl MouseInput { .unwrap_or_default(); let pressed = !self.down && new.mouse_down; + let released = self.down && !new.mouse_down; + let click = released && self.could_be_click; + let double_click = click && (new.time - self.last_click_time) < MAX_CLICK_DELAY; let mut press_origin = self.press_origin; let mut could_be_click = self.could_be_click; + let mut last_click_time = self.last_click_time; + if click { + last_click_time = new.time + } if pressed { press_origin = new.mouse_pos; @@ -192,8 +218,6 @@ impl MouseInput { } if let (Some(press_origin), Some(mouse_pos)) = (new.mouse_pos, press_origin) { - // If mouse moves more than this, it is no longer a click (but maybe a drag) - const MAX_CLICK_DIST: f32 = 6.0; could_be_click &= press_origin.distance(mouse_pos) < MAX_CLICK_DIST; } else { could_be_click = false; @@ -212,10 +236,13 @@ impl MouseInput { MouseInput { down: new.mouse_down && new.mouse_pos.is_some(), pressed, - released: self.down && !new.mouse_down, + released, + could_be_click, + click, + double_click, + last_click_time, pos: new.mouse_pos, press_origin, - could_be_click, delta, velocity, pos_tracker: self.pos_tracker, @@ -281,9 +308,12 @@ impl MouseInput { ui.add(label!("down: {}", self.down)); ui.add(label!("pressed: {}", self.pressed)); ui.add(label!("released: {}", self.released)); + ui.add(label!("could_be_click: {}", self.could_be_click)); + ui.add(label!("click: {}", self.click)); + ui.add(label!("double_click: {}", self.double_click)); + ui.add(label!("last_click_time: {:.3}", self.last_click_time)); ui.add(label!("pos: {:?}", self.pos)); ui.add(label!("press_origin: {:?}", self.press_origin)); - ui.add(label!("could_be_click: {}", self.could_be_click)); ui.add(label!("delta: {:?}", self.delta)); ui.add(label!( "velocity: [{:3.0} {:3.0}] points/sec", diff --git a/emigui/src/types.rs b/emigui/src/types.rs index 3b20340b..1cc971e4 100644 --- a/emigui/src/types.rs +++ b/emigui/src/types.rs @@ -46,6 +46,8 @@ pub struct InteractInfo { /// The mouse pressed this thing ealier, and now released on this thing too. pub clicked: bool, + pub double_clicked: bool, + /// The mouse is interacting with this thing (e.g. dragging it or holding it) pub active: bool, @@ -58,6 +60,7 @@ impl InteractInfo { Self { hovered: false, clicked: false, + double_clicked: false, active: false, rect: Rect::nothing(), } @@ -67,6 +70,7 @@ impl InteractInfo { Self { hovered: self.hovered || other.hovered, clicked: self.clicked || other.clicked, + double_clicked: self.double_clicked || other.double_clicked, active: self.active || other.active, rect: self.rect.union(other.rect), } @@ -83,6 +87,8 @@ pub struct GuiResponse { /// The mouse clicked this thing this frame pub clicked: bool, + pub double_clicked: bool, + /// The mouse is interacting with this thing (e.g. dragging it) pub active: bool, @@ -115,6 +121,7 @@ impl Into for GuiResponse { InteractInfo { hovered: self.hovered, clicked: self.clicked, + double_clicked: self.double_clicked, active: self.active, rect: self.rect, } diff --git a/emigui/src/ui.rs b/emigui/src/ui.rs index 3d5686ad..ded79ecb 100644 --- a/emigui/src/ui.rs +++ b/emigui/src/ui.rs @@ -323,6 +323,7 @@ impl Ui { GuiResponse { hovered: interact.hovered, clicked: interact.clicked, + double_clicked: interact.double_clicked, active: interact.active, rect: interact.rect, ctx: self.ctx.clone(),