Browse Source

Highlight window frame when you resize it

readable-ids
Emil Ernerfeldt 5 years ago
parent
commit
ee0ad02717
  1. 18
      emigui/src/containers/area.rs
  2. 6
      emigui/src/containers/collapsing_header.rs
  3. 62
      emigui/src/containers/frame.rs
  4. 8
      emigui/src/containers/resize.rs
  5. 12
      emigui/src/containers/scroll_area.rs
  6. 200
      emigui/src/containers/window.rs
  7. 4
      emigui/src/examples/app.rs
  8. 42
      emigui/src/paint/mesher.rs
  9. 2
      emigui/src/style.rs
  10. 24
      emigui/src/widgets.rs

18
emigui/src/containers/area.rs

@ -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();

6
emigui/src/containers/collapsing_header.rs

@ -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);

62
emigui/src/containers/frame.rs

@ -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 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,
}
}
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 mut prepared = self.begin(ui);
let ret = add_contents(&mut prepared.content_ui);
prepared.end(ui);
ret
}
}
let outer_rect = ui.available();
let inner_rect = outer_rect.shrink2(margin);
let where_to_put_background = ui.paint_list_len();
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,
)
}
let mut child_ui = ui.child_ui(inner_rect);
let ret = add_contents(&mut child_ui);
pub fn end(self, ui: &mut Ui) -> Rect {
let outer_rect = self.outer_rect();
let outer_rect = Rect::from_min_max(outer_rect.min, child_ui.child_bounds().max + margin);
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
}
}

8
emigui/src/containers/resize.rs

@ -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,

12
emigui/src/containers/scroll_area.rs

@ -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();

200
emigui/src/containers/window.rs

@ -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;
}
}
})
});
let resizable =
resizable && collapsing_header::State::is_open(ctx, collapsing_id).unwrap_or_default();
if movable || resizable {
let possible = PossibleInteractions { movable, resizable };
interact(
ctx,
possible,
area_layer,
window_id,
resize_id,
full_interact.rect,
);
title_bar.title_ui(ui);
}
let outer_rect = frame.end(&mut area.content_ui);
let interaction = if possible.movable || possible.resizable {
interact(
ctx,
possible,
area_layer,
&mut area.state,
window_id,
resize_id,
outer_rect,
)
} else {
None
};
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 {

4
emigui/src/examples/app.rs

@ -497,10 +497,10 @@ impl LayoutExample {
pub fn ui(&mut self, ui: &mut Ui) {
Resize::default()
.default_size(vec2(200.0, 200.0))
.show(ui, |ui| self.contents_ui(ui));
.show(ui, |ui| self.content_ui(ui));
}
pub fn contents_ui(&mut self, ui: &mut Ui) {
pub fn content_ui(&mut self, ui: &mut Ui) {
let layout = Layout::from_dir_align(self.dir, self.align);
if self.reversed {
ui.set_layout(layout.reverse());

42
emigui/src/paint/mesher.rs

@ -163,6 +163,14 @@ impl Path {
self.0.clear();
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
#[inline(always)]
pub fn add_point(&mut self, pos: Pos2, normal: Vec2) {
self.0.push(PathPoint { pos, normal });
@ -252,13 +260,13 @@ impl Path {
/// with x right, and y down (GUI coords) we have:
/// angle = dir
/// 0 * TAU / 4 = right
/// quadrant 0, right down
/// 1 * TAU / 4 = down
/// quadrant 1, down left
/// quadrant 0, right bottom
/// 1 * TAU / 4 = bottom
/// quadrant 1, left bottom
/// 2 * TAU / 4 = left
/// quadrant 2 left up
/// 3 * TAU / 4 = up
/// quadrant 3 up rigth
/// quadrant 2 left top
/// 3 * TAU / 4 = top
/// quadrant 3 right top
/// 4 * TAU / 4 = right
pub fn add_circle_quadrant(&mut self, center: Pos2, radius: f32, quadrant: f32) {
let n = (radius * 0.5).round() as i32; // TODO: tweak a bit more
@ -581,16 +589,18 @@ pub fn paint_command_into_triangles(
fill_color,
outline,
} => {
if let Some(fill_color) = fill_color {
debug_assert!(
closed,
"You asked to fill a path that is not closed. That makes no sense."
);
fill_closed_path(out, options, &path.0, fill_color);
}
if let Some(outline) = outline {
let typ = if closed { Closed } else { Open };
paint_path_outline(out, options, typ, &path.0, outline.color, outline.width);
if path.len() >= 2 {
if let Some(fill_color) = fill_color {
debug_assert!(
closed,
"You asked to fill a path that is not closed. That makes no sense."
);
fill_closed_path(out, options, &path.0, fill_color);
}
if let Some(outline) = outline {
let typ = if closed { Closed } else { Open };
paint_path_outline(out, options, typ, &path.0, outline.color, outline.width);
}
}
}
PaintCmd::Rect {

2
emigui/src/style.rs

@ -120,7 +120,7 @@ impl Default for Interact {
fill_color: srgba(60, 60, 80, 255),
stroke_color: gray(210, 255), // Mustn't look grayed out!
stroke_width: 1.0,
rect_outline: Some(Outline::new(0.5, WHITE)),
rect_outline: Some(Outline::new(1.0, white(128))),
corner_radius: 0.0,
},
}

24
emigui/src/widgets.rs

@ -63,7 +63,16 @@ impl Label {
self
}
pub fn layout(&self, max_width: f32, ui: &Ui) -> font::Galley {
pub fn layout(&self, ui: &Ui) -> font::Galley {
let max_width = if self.auto_shrink {
ui.available_finite().width()
} else {
ui.available().width()
};
self.layout_width(ui, max_width)
}
pub fn layout_width(&self, ui: &Ui, max_width: f32) -> font::Galley {
let font = &ui.fonts()[self.text_style];
if self.multiline {
font.layout_multiline(self.text.clone(), max_width) // TODO: avoid clone
@ -83,6 +92,10 @@ impl Label {
// TODO: a paint method for painting anywhere in a ui.
// This should be the easiest method of putting text anywhere.
pub fn paint_galley(&self, ui: &mut Ui, pos: Pos2, galley: font::Galley) {
ui.add_galley(pos, galley, self.text_style, self.text_color);
}
}
/// Usage: label!("Foo: {}", bar)
@ -94,14 +107,9 @@ macro_rules! label {
impl Widget for Label {
fn ui(self, ui: &mut Ui) -> GuiResponse {
let max_width = if self.auto_shrink {
ui.available_finite().width()
} else {
ui.available().width()
};
let galley = self.layout(max_width, ui);
let galley = self.layout(ui);
let interact = ui.reserve_space(galley.size, None);
ui.add_galley(interact.rect.min, galley, self.text_style, self.text_color);
self.paint_galley(ui, interact.rect.min, galley);
ui.response(interact)
}
}

Loading…
Cancel
Save