[window] double-click title bar to collapse winodw

This commit is contained in:
Emil Ernerfeldt 2020-05-23 21:52:03 +02:00
parent 6ab7cffd7f
commit 19cbe6bd6e
5 changed files with 119 additions and 65 deletions

View file

@ -168,51 +168,54 @@ impl<'open> Window<'open> {
let mut area = area.begin(ctx); let mut area = area.begin(ctx);
{ {
// BEGIN FRAME --------------------------------
let mut frame = frame.begin(&mut area.content_ui); let mut frame = frame.begin(&mut area.content_ui);
let content_rect; let default_expanded = true;
let title_bar; let mut collapsing = collapsing_header::State::from_memory_with_default_open(
{ &mut frame.content_ui,
let ui = &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 content_rect = collapsing
let mut collapsing = collapsing_header::State::from_memory_with_default_open( .add_contents(&mut frame.content_ui, |ui| {
ui, resize.show(ui, |ui| {
collapsing_id, // Add some spacing (item_spacing) between title and content:
default_expanded, ui.allocate_space(Vec2::zero());
);
let show_close_button = open.is_some();
title_bar = show_title_bar(
ui,
title_label,
show_close_button,
collapsing_id,
&mut collapsing,
);
content_rect = collapsing if let Some(scroll) = scroll {
.add_contents(ui, |ui| { scroll.show(ui, add_contents)
resize.show(ui, |ui| { } else {
// Add some spacing (item_spacing) between title and content: add_contents(ui)
ui.allocate_space(Vec2::zero()); }
if let Some(scroll) = scroll {
scroll.show(ui, add_contents)
} else {
add_contents(ui)
}
})
}) })
.map(|ri| ri.1); })
.map(|ri| ri.1);
ui.memory()
.collapsing_headers
.insert(collapsing_id, collapsing);
}
let outer_rect = frame.end(&mut area.content_ui); 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 { let interaction = if possible.movable || possible.resizable {
interact( interact(
@ -524,7 +527,7 @@ fn show_title_bar(
collapsing_id: Id, collapsing_id: Id,
collapsing: &mut collapsing_header::State, collapsing: &mut collapsing_header::State,
) -> TitleBar { ) -> 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)); ui.set_desired_height(title_label.font_height(ui));
let item_spacing = ui.style().item_spacing; 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 rect = ui.allocate_space(Vec2::splat(button_size));
let collapse_button_interact = ui.interact(rect, collapsing_id, Sense::click()); let collapse_button_interact = ui.interact(rect, collapsing_id, Sense::click());
if collapse_button_interact.clicked { if collapse_button_interact.clicked {
// TODO: also do this when double-clicking window title
collapsing.toggle(ui); collapsing.toggle(ui);
} }
collapsing.paint_icon(ui, &collapse_button_interact); collapsing.paint_icon(ui, &collapse_button_interact);
@ -569,28 +571,35 @@ fn show_title_bar(
}); });
TitleBar { TitleBar {
rect: tb_interact.1, rect: title_bar_and_rect.1,
..tb_interact.0 ..title_bar_and_rect.0
} }
} }
impl TitleBar { impl TitleBar {
fn ui( fn ui(
self, mut self,
ui: &mut Ui, ui: &mut Ui,
outer_rect: Rect, outer_rect: Rect,
content_rect: Option<Rect>, content_rect: Option<Rect>,
open: Option<&mut bool>, 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 { if let Some(open) = open {
// Add close button now that we know our full width: // 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; *open = false;
} }
} }
// TODO: pick style for title based on move interaction // 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 { if let Some(content_rect) = content_rect {
// paint separator between title and content: // paint separator between title and content:
@ -602,20 +611,21 @@ impl TitleBar {
style: ui.style().interact.inactive.rect_outline.unwrap(), 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) { fn close_button_ui(&self, ui: &mut Ui) -> InteractInfo {
self.title_label
.paint_galley(ui, self.title_rect.min, self.title_galley);
}
fn close_button_ui(&self, ui: &mut Ui, content_rect: &Option<Rect>) -> InteractInfo {
let right = content_rect.map(|c| c.right()).unwrap_or(self.rect.right());
let button_size = ui.style().start_icon_width; let button_size = ui.style().start_icon_width;
let button_rect = Rect::from_min_size( let button_rect = Rect::from_min_size(
pos2( 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, self.rect.center().y - 0.5 * button_size,
), ),
Vec2::splat(button_size), Vec2::splat(button_size),

View file

@ -36,7 +36,6 @@ pub struct Context {
paint_stats: Mutex<PaintStats>, paint_stats: Mutex<PaintStats>,
} }
// TODO: remove this impl.
impl Clone for Context { impl Clone for Context {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Context { Context {
@ -280,6 +279,7 @@ impl Context {
rect, rect,
hovered, hovered,
clicked: false, clicked: false,
double_clicked: false,
active: false, active: false,
}; };
} }
@ -299,6 +299,7 @@ impl Context {
rect, rect,
hovered: true, hovered: true,
clicked: false, clicked: false,
double_clicked: false,
active: false, active: false,
}; };
@ -321,14 +322,17 @@ impl Context {
rect, rect,
hovered, hovered,
clicked: false, clicked: false,
double_clicked: false,
active: false, active: false,
} }
} }
} else if self.input.mouse.released { } else if self.input.mouse.released {
let clicked = hovered && active;
InteractInfo { InteractInfo {
rect, rect,
hovered, hovered,
clicked: hovered && active, clicked,
double_clicked: clicked && self.input.mouse.double_click,
active, active,
} }
} else if self.input.mouse.down { } else if self.input.mouse.down {
@ -336,6 +340,7 @@ impl Context {
rect, rect,
hovered: hovered && active, hovered: hovered && active,
clicked: false, clicked: false,
double_clicked: false,
active, active,
} }
} else { } else {
@ -343,6 +348,7 @@ impl Context {
rect, rect,
hovered, hovered,
clicked: false, clicked: false,
double_clicked: false,
active, active,
} }
} }

View file

@ -2,6 +2,11 @@ use serde_derive::Deserialize;
use crate::{math::*, movement_tracker::MovementTracker}; 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. /// What the integration gives to the gui.
/// All coordinates in emigui is in point/logical coordinates. /// All coordinates in emigui is in point/logical coordinates.
#[derive(Clone, Debug, Default, Deserialize)] #[derive(Clone, Debug, Default, Deserialize)]
@ -77,6 +82,21 @@ pub struct MouseInput {
/// The mouse went from down to !down /// The mouse went from down to !down
pub released: bool, 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. /// Current position of the mouse in points.
/// None for touch screens when finger is not down. /// None for touch screens when finger is not down.
pub pos: Option<Pos2>, pub pos: Option<Pos2>,
@ -84,10 +104,6 @@ pub struct MouseInput {
/// Where did the current click/drag originate? /// Where did the current click/drag originate?
pub press_origin: Option<Pos2>, pub press_origin: Option<Pos2>,
/// 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. /// How much the mouse moved compared to last frame, in points.
pub delta: Vec2, pub delta: Vec2,
@ -105,9 +121,12 @@ impl Default for MouseInput {
down: false, down: false,
pressed: false, pressed: false,
released: false, released: false,
could_be_click: false,
click: false,
double_click: false,
last_click_time: std::f64::NEG_INFINITY,
pos: None, pos: None,
press_origin: None, press_origin: None,
could_be_click: false,
delta: Vec2::zero(), delta: Vec2::zero(),
velocity: Vec2::zero(), velocity: Vec2::zero(),
pos_tracker: MovementTracker::new(1000, 0.1), pos_tracker: MovementTracker::new(1000, 0.1),
@ -181,8 +200,15 @@ impl MouseInput {
.unwrap_or_default(); .unwrap_or_default();
let pressed = !self.down && new.mouse_down; 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 press_origin = self.press_origin;
let mut could_be_click = self.could_be_click; 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 { if pressed {
press_origin = new.mouse_pos; 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 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; could_be_click &= press_origin.distance(mouse_pos) < MAX_CLICK_DIST;
} else { } else {
could_be_click = false; could_be_click = false;
@ -212,10 +236,13 @@ impl MouseInput {
MouseInput { MouseInput {
down: new.mouse_down && new.mouse_pos.is_some(), down: new.mouse_down && new.mouse_pos.is_some(),
pressed, pressed,
released: self.down && !new.mouse_down, released,
could_be_click,
click,
double_click,
last_click_time,
pos: new.mouse_pos, pos: new.mouse_pos,
press_origin, press_origin,
could_be_click,
delta, delta,
velocity, velocity,
pos_tracker: self.pos_tracker, pos_tracker: self.pos_tracker,
@ -281,9 +308,12 @@ impl MouseInput {
ui.add(label!("down: {}", self.down)); ui.add(label!("down: {}", self.down));
ui.add(label!("pressed: {}", self.pressed)); ui.add(label!("pressed: {}", self.pressed));
ui.add(label!("released: {}", self.released)); 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!("pos: {:?}", self.pos));
ui.add(label!("press_origin: {:?}", self.press_origin)); 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!("delta: {:?}", self.delta));
ui.add(label!( ui.add(label!(
"velocity: [{:3.0} {:3.0}] points/sec", "velocity: [{:3.0} {:3.0}] points/sec",

View file

@ -46,6 +46,8 @@ pub struct InteractInfo {
/// The mouse pressed this thing ealier, and now released on this thing too. /// The mouse pressed this thing ealier, and now released on this thing too.
pub clicked: bool, pub clicked: bool,
pub double_clicked: bool,
/// The mouse is interacting with this thing (e.g. dragging it or holding it) /// The mouse is interacting with this thing (e.g. dragging it or holding it)
pub active: bool, pub active: bool,
@ -58,6 +60,7 @@ impl InteractInfo {
Self { Self {
hovered: false, hovered: false,
clicked: false, clicked: false,
double_clicked: false,
active: false, active: false,
rect: Rect::nothing(), rect: Rect::nothing(),
} }
@ -67,6 +70,7 @@ impl InteractInfo {
Self { Self {
hovered: self.hovered || other.hovered, hovered: self.hovered || other.hovered,
clicked: self.clicked || other.clicked, clicked: self.clicked || other.clicked,
double_clicked: self.double_clicked || other.double_clicked,
active: self.active || other.active, active: self.active || other.active,
rect: self.rect.union(other.rect), rect: self.rect.union(other.rect),
} }
@ -83,6 +87,8 @@ pub struct GuiResponse {
/// The mouse clicked this thing this frame /// The mouse clicked this thing this frame
pub clicked: bool, pub clicked: bool,
pub double_clicked: bool,
/// The mouse is interacting with this thing (e.g. dragging it) /// The mouse is interacting with this thing (e.g. dragging it)
pub active: bool, pub active: bool,
@ -115,6 +121,7 @@ impl Into<InteractInfo> for GuiResponse {
InteractInfo { InteractInfo {
hovered: self.hovered, hovered: self.hovered,
clicked: self.clicked, clicked: self.clicked,
double_clicked: self.double_clicked,
active: self.active, active: self.active,
rect: self.rect, rect: self.rect,
} }

View file

@ -323,6 +323,7 @@ impl Ui {
GuiResponse { GuiResponse {
hovered: interact.hovered, hovered: interact.hovered,
clicked: interact.clicked, clicked: interact.clicked,
double_clicked: interact.double_clicked,
active: interact.active, active: interact.active,
rect: interact.rect, rect: interact.rect,
ctx: self.ctx.clone(), ctx: self.ctx.clone(),