1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-01 22:30:03 -04:00

Highlight window frame when you resize it

This commit is contained in:
Emil Ernerfeldt
2020-05-23 11:28:21 +02:00
parent fac0866f73
commit ee0ad02717
10 changed files with 263 additions and 111 deletions

View File

@@ -91,15 +91,15 @@ impl Area {
}
}
struct Prepared {
pub(crate) struct Prepared {
layer: Layer,
state: State,
pub(crate) state: State,
movable: bool,
content_ui: Ui,
pub(crate) content_ui: Ui,
}
impl Area {
fn prepare(self, ctx: &Arc<Context>) -> Prepared {
pub(crate) fn begin(self, ctx: &Arc<Context>) -> Prepared {
let Area {
id,
movable,
@@ -138,18 +138,20 @@ impl Area {
}
pub fn show(self, ctx: &Arc<Context>, add_contents: impl FnOnce(&mut Ui)) -> InteractInfo {
let mut prepared = self.prepare(ctx);
let mut prepared = self.begin(ctx);
add_contents(&mut prepared.content_ui);
Self::finish(ctx, prepared)
prepared.end(ctx)
}
}
fn finish(ctx: &Arc<Context>, prepared: Prepared) -> InteractInfo {
impl Prepared {
pub(crate) fn end(self, ctx: &Arc<Context>) -> InteractInfo {
let Prepared {
layer,
mut state,
movable,
content_ui,
} = prepared;
} = self;
state.size = (content_ui.child_bounds().max - state.pos).ceil();

View File

@@ -168,7 +168,7 @@ struct Prepared {
}
impl CollapsingHeader {
fn prepare(self, ui: &mut Ui) -> Prepared {
fn begin(self, ui: &mut Ui) -> Prepared {
assert!(
ui.layout().dir() == Direction::Vertical,
"Horizontal collapsing is unimplemented"
@@ -185,7 +185,7 @@ impl CollapsingHeader {
let available = ui.available_finite();
let text_pos = available.min + vec2(ui.style().indent, 0.0);
let galley = label.layout(available.width() - ui.style().indent, ui);
let galley = label.layout_width(ui, available.width() - ui.style().indent);
let text_max_x = text_pos.x + galley.size.x;
let desired_width = text_max_x - available.left();
let desired_width = desired_width.max(available.width());
@@ -240,7 +240,7 @@ impl CollapsingHeader {
}
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Option<R> {
let Prepared { id, mut state } = self.prepare(ui);
let Prepared { id, mut state } = self.begin(ui);
let r_interact = state.add_contents(ui, |ui| ui.indent(id, add_contents).0);
let ret = r_interact.map(|ri| ri.0);
ui.memory().collapsing_headers.insert(id, state);

View File

@@ -17,7 +17,7 @@ impl Frame {
margin: style.window_padding,
corner_radius: style.window.corner_radius,
fill_color: Some(style.background_fill_color),
outline: Some(Outline::new(1.0, color::WHITE)),
outline: style.interact.inactive.rect_outline, // becauce we can resize windows
}
}
@@ -59,30 +59,58 @@ impl Frame {
}
}
pub struct Prepared {
pub frame: Frame,
outer_rect_bounds: Rect,
where_to_put_background: usize,
pub content_ui: Ui,
}
impl Frame {
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
let Frame {
margin,
corner_radius,
fill_color,
outline,
} = self;
let outer_rect = ui.available();
let inner_rect = outer_rect.shrink2(margin);
pub fn begin(self, ui: &mut Ui) -> Prepared {
let outer_rect_bounds = ui.available();
let inner_rect = outer_rect_bounds.shrink2(self.margin);
let where_to_put_background = ui.paint_list_len();
let content_ui = ui.child_ui(inner_rect);
Prepared {
frame: self,
outer_rect_bounds,
where_to_put_background,
content_ui,
}
}
let mut child_ui = ui.child_ui(inner_rect);
let ret = add_contents(&mut child_ui);
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
let mut prepared = self.begin(ui);
let ret = add_contents(&mut prepared.content_ui);
prepared.end(ui);
ret
}
}
let outer_rect = Rect::from_min_max(outer_rect.min, child_ui.child_bounds().max + margin);
impl Prepared {
pub fn outer_rect(&self) -> Rect {
Rect::from_min_max(
self.outer_rect_bounds.min,
self.content_ui.child_bounds().max + self.frame.margin,
)
}
pub fn end(self, ui: &mut Ui) -> Rect {
let outer_rect = self.outer_rect();
let Prepared {
frame,
where_to_put_background,
..
} = self;
ui.insert_paint_cmd(
where_to_put_background,
PaintCmd::Rect {
corner_radius,
fill_color,
outline,
corner_radius: frame.corner_radius,
fill_color: frame.fill_color,
outline: frame.outline,
rect: outer_rect,
},
);
@@ -90,6 +118,6 @@ impl Frame {
ui.expand_to_include_child(outer_rect);
// TODO: move cursor in parent ui
ret
outer_rect
}
}

View File

@@ -177,7 +177,7 @@ struct Prepared {
}
impl Resize {
fn prepare(&mut self, ui: &mut Ui) -> Prepared {
fn begin(&mut self, ui: &mut Ui) -> Prepared {
let id = self.id.unwrap_or_else(|| ui.make_child_id("resize"));
self.min_size = self.min_size.min(ui.available().size());
self.max_size = self.max_size.min(ui.available().size());
@@ -267,13 +267,13 @@ impl Resize {
}
pub fn show<R>(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
let mut prepared = self.prepare(ui);
let mut prepared = self.begin(ui);
let ret = add_contents(&mut prepared.content_ui);
self.finish(ui, prepared);
self.end(ui, prepared);
ret
}
fn finish(self, ui: &mut Ui, prepared: Prepared) {
fn end(self, ui: &mut Ui, prepared: Prepared) {
let Prepared {
id,
mut state,

View File

@@ -54,7 +54,7 @@ struct Prepared {
}
impl ScrollArea {
fn prepare(self, ui: &mut Ui) -> Prepared {
fn begin(self, ui: &mut Ui) -> Prepared {
let Self {
max_height,
always_show_scroll,
@@ -110,13 +110,15 @@ impl ScrollArea {
}
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
let mut prepared = self.prepare(ui);
let mut prepared = self.begin(ui);
let ret = add_contents(&mut prepared.content_ui);
Self::finish(ui, prepared);
prepared.end(ui);
ret
}
}
fn finish(ui: &mut Ui, prepared: Prepared) {
impl Prepared {
fn end(self, ui: &mut Ui) {
let Prepared {
id,
mut state,
@@ -124,7 +126,7 @@ impl ScrollArea {
always_show_scroll,
current_scroll_bar_width,
content_ui,
} = prepared;
} = self;
let content_size = content_ui.bounding_size();

View File

@@ -148,22 +148,31 @@ impl<'open> Window<'open> {
return None;
}
let movable = area.is_movable();
let area = area.movable(false); // We move it manually
let resizable = resize.is_resizable();
let resize = resize.resizable(false); // We move it manually
let window_id = Id::new(title_label.text());
let area_layer = area.layer();
let resize_id = window_id.with("resize");
let collapsing_id = window_id.with("collapsing");
let possible = PossibleInteractions {
movable: area.is_movable(),
resizable: resize.is_resizable()
&& collapsing_header::State::is_open(ctx, collapsing_id).unwrap_or_default(),
};
let area = area.movable(false); // We move it manually
let resize = resize.resizable(false); // We move it manually
let resize = resize.id(resize_id);
let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));
let full_interact = area.show(ctx, |ui| {
frame.show(ui, |ui| {
let mut area = area.begin(ctx);
{
// TODO: pick style for frame and title based on interaction
let mut frame = frame.begin(&mut area.content_ui);
{
let ui = &mut frame.content_ui;
let default_expanded = true;
let mut collapsing = collapsing_header::State::from_memory_with_default_open(
ui,
@@ -182,7 +191,7 @@ impl<'open> Window<'open> {
let content = collapsing
.add_contents(ui, |ui| {
resize.show(ui, |ui| {
ui.add(Separator::new().line_width(1.0)); // TODO: nicer way to split window title from contents
ui.add(Separator::new().line_width(0.5)); // TODO: nicer way to split window title from contents
if let Some(scroll) = scroll {
scroll.show(ui, add_contents)
} else {
@@ -198,42 +207,49 @@ impl<'open> Window<'open> {
if let Some(open) = open {
// Add close button now that we know our full width:
let right = content
.map(|c| c.rect.right())
.unwrap_or(title_bar.rect.right());
let button_size = ui.style().start_icon_width;
let button_rect = Rect::from_min_size(
pos2(
right - ui.style().item_spacing.x - button_size,
title_bar.rect.center().y - 0.5 * button_size,
),
Vec2::splat(button_size),
);
if close_button(ui, button_rect).clicked {
if title_bar.close_button_ui(ui, &content).clicked {
*open = false;
}
}
})
});
title_bar.title_ui(ui);
}
let resizable =
resizable && collapsing_header::State::is_open(ctx, collapsing_id).unwrap_or_default();
let outer_rect = frame.end(&mut area.content_ui);
if movable || resizable {
let possible = PossibleInteractions { movable, resizable };
let interaction = if possible.movable || possible.resizable {
interact(
ctx,
possible,
area_layer,
&mut area.state,
window_id,
resize_id,
outer_rect,
)
} else {
None
};
interact(
ctx,
possible,
area_layer,
window_id,
resize_id,
full_interact.rect,
);
if let Some(interaction) = interaction {
paint_frame_interaction(
&mut area.content_ui,
outer_rect,
interaction,
ctx.style().interact.active,
);
} else {
if let Some(hover_interaction) = resize_hover(ctx, possible, area_layer, outer_rect)
{
paint_frame_interaction(
&mut area.content_ui,
outer_rect,
hover_interaction,
ctx.style().interact.hovered,
);
}
}
}
let full_interact = area.end(ctx);
Some(full_interact)
}
@@ -280,10 +296,11 @@ fn interact(
ctx: &Context,
possible: PossibleInteractions,
area_layer: Layer,
area_state: &mut area::State,
window_id: Id,
resize_id: Id,
rect: Rect,
) -> Option<()> {
) -> Option<WindowInteraction> {
let pre_resize = ctx.round_rect_to_pixels(rect);
let window_interaction = window_interaction(
ctx,
@@ -297,9 +314,7 @@ fn interact(
let new_rect = ctx.round_rect_to_pixels(new_rect);
// TODO: add this to a Window state instead as a command "move here next frame"
let mut area_state = ctx.memory().areas.get(area_layer.id).unwrap();
area_state.pos = new_rect.min;
ctx.memory().areas.set_state(area_layer, area_state);
let mut resize_state = ctx.memory().resize.get(&resize_id).cloned().unwrap();
// resize_state.size += new_rect.size() - pre_resize.size();
@@ -308,7 +323,7 @@ fn interact(
ctx.memory().resize.insert(resize_id, resize_state);
ctx.memory().areas.move_to_top(area_layer);
Some(())
Some(window_interaction)
}
fn resize_window(ctx: &Context, window_interaction: &WindowInteraction) -> Option<Rect> {
@@ -446,16 +461,69 @@ fn resize_hover(
}
}
/// Fill in parts of the window frame when we resize by dragging that part
fn paint_frame_interaction(
ui: &mut Ui,
rect: Rect,
interaction: WindowInteraction,
style: style::WidgetStyle,
) {
let cr = ui.style().window.corner_radius;
let Rect { min, max } = rect;
let mut path = Path::default();
if interaction.right && !interaction.bottom && !interaction.top {
path.add_line(&[pos2(max.x, min.y + cr), pos2(max.x, max.y - cr)]);
}
if interaction.right && interaction.bottom {
path.add_line(&[pos2(max.x, min.y + cr), pos2(max.x, max.y - cr)]);
path.add_circle_quadrant(pos2(max.x - cr, max.y - cr), cr, 0.0);
}
if interaction.bottom {
path.add_line(&[pos2(max.x - cr, max.y), pos2(min.x + cr, max.y)]);
}
if interaction.left && interaction.bottom {
path.add_circle_quadrant(pos2(min.x + cr, max.y - cr), cr, 1.0);
}
if interaction.left {
path.add_line(&[pos2(min.x, max.y - cr), pos2(min.x, min.y + cr)]);
}
if interaction.left && interaction.top {
path.add_circle_quadrant(pos2(min.x + cr, min.y + cr), cr, 2.0);
}
if interaction.top {
path.add_line(&[pos2(min.x + cr, min.y), pos2(max.x - cr, min.y)]);
}
if interaction.right && interaction.top {
path.add_circle_quadrant(pos2(max.x - cr, min.y + cr), cr, 3.0);
path.add_line(&[pos2(max.x, min.y + cr), pos2(max.x, max.y - cr)]);
}
ui.add_paint_cmd(PaintCmd::Path {
path,
closed: false,
fill_color: None,
outline: style.rect_outline,
});
}
// ----------------------------------------------------------------------------
struct TitleBar {
title_label: Label,
title_galley: font::Galley,
title_rect: Rect,
rect: Rect,
}
fn show_title_bar(
ui: &mut Ui,
title_label: Label,
show_close_button: bool,
collapsing_id: Id,
collapsing: &mut collapsing_header::State,
) -> InteractInfo {
ui.inner_layout(Layout::horizontal(Align::Center), |ui| {
) -> TitleBar {
let tb_interact = ui.inner_layout(Layout::horizontal(Align::Center), |ui| {
ui.set_desired_height(title_label.font_height(ui));
let item_spacing = ui.style().item_spacing;
@@ -474,7 +542,8 @@ fn show_title_bar(
collapsing.paint_icon(ui, &collapse_button_interact);
}
let title_rect = ui.add(title_label).rect;
let title_galley = title_label.layout(ui);
let title_rect = ui.reserve_space(title_galley.size, None).rect;
if show_close_button {
// Reserve space for close button which will be added later:
@@ -489,8 +558,41 @@ fn show_title_bar(
);
ui.expand_to_include_child(close_rect);
}
})
.1
TitleBar {
title_label,
title_galley,
title_rect,
rect: Default::default(), // Will be filled in later
}
});
TitleBar {
rect: tb_interact.1.rect,
..tb_interact.0
}
}
impl TitleBar {
pub fn title_ui(self, ui: &mut Ui) {
self.title_label
.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());
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.center().y - 0.5 * button_size,
),
Vec2::splat(button_size),
);
close_button(ui, button_rect)
}
}
fn close_button(ui: &mut Ui, rect: Rect) -> InteractInfo {