diff --git a/emigui/src/containers/area.rs b/emigui/src/containers/area.rs index 6f888947..93972f45 100644 --- a/emigui/src/containers/area.rs +++ b/emigui/src/containers/area.rs @@ -14,6 +14,10 @@ pub(crate) struct State { /// Last know size. Used for catching clicks. pub size: Vec2, + /// If false, clicks goes stright throught to what is behind us. + /// Good for tooltips etc. + pub interactable: bool, + /// You can throw a moveable Area. It's fun. /// TODO: separate out moveable to container? #[serde(skip)] @@ -24,7 +28,8 @@ pub(crate) struct State { pub struct Area { id: Id, movable: bool, - always_on_top: bool, + interactable: bool, + order: Order, default_pos: Option, fixed_pos: Option, } @@ -34,7 +39,8 @@ impl Area { Self { id: Id::new(id_source), movable: true, - always_on_top: false, + interactable: true, + order: Order::Middle, default_pos: None, fixed_pos: None, } @@ -42,12 +48,21 @@ impl Area { pub fn movable(mut self, movable: bool) -> Self { self.movable = movable; + self.interactable |= movable; self } - /// Always show as top Area - pub fn always_on_top(mut self) -> Self { - self.always_on_top = true; + /// If false, clicks goes stright throught to what is behind us. + /// Good for tooltips etc. + pub fn interactable(mut self, interactable: bool) -> Self { + self.interactable = interactable; + self.movable &= interactable; + self + } + + /// `order(Order::Foreground)` for an Area that should always be on top + pub fn order(mut self, order: Order) -> Self { + self.order = order; self } @@ -74,14 +89,15 @@ impl Area { let Area { id, movable, - always_on_top, + order, + interactable, default_pos, fixed_pos, } = self; let default_pos = default_pos.unwrap_or_else(|| pos2(100.0, 100.0)); // TODO let id = ctx.register_unique_id(id, "Area", default_pos); - let layer = Layer::Window(id); + let layer = Layer { order, id }; let (mut state, _is_new) = match ctx.memory().get_area(id) { Some(state) => (state, false), @@ -89,6 +105,7 @@ impl Area { let state = State { pos: default_pos, size: Vec2::zero(), + interactable, vel: Vec2::zero(), }; (state, true) @@ -144,18 +161,18 @@ impl Area { // &format!("Area size: {:?}", state.size), // ); - if move_interact.active || mouse_pressed_on_area(ctx, id) || always_on_top { - ctx.memory().move_area_to_top(id); + if move_interact.active || mouse_pressed_on_area(ctx, layer) { + ctx.memory().move_area_to_top(layer); } - ctx.memory().set_area_state(id, state); + ctx.memory().set_area_state(layer, state); move_interact } } -fn mouse_pressed_on_area(ctx: &Context, id: Id) -> bool { +fn mouse_pressed_on_area(ctx: &Context, layer: Layer) -> bool { if let Some(mouse_pos) = ctx.input().mouse_pos { - ctx.input().mouse_pressed && ctx.memory().layer_at(mouse_pos) == Layer::Window(id) + ctx.input().mouse_pressed && ctx.memory().layer_at(mouse_pos) == Some(layer) } else { false } diff --git a/emigui/src/containers/frame.rs b/emigui/src/containers/frame.rs index 158e5289..628f7fca 100644 --- a/emigui/src/containers/frame.rs +++ b/emigui/src/containers/frame.rs @@ -28,6 +28,15 @@ impl Frame { outline: Some(Outline::new(1.0, color::white(128))), } } + + pub fn popup(style: &Style) -> Self { + Self { + margin: style.window_padding, + corner_radius: 5.0, + fill_color: Some(style.background_fill_color()), + outline: Some(Outline::new(1.0, color::white(128))), + } + } } impl Frame { diff --git a/emigui/src/containers/resize.rs b/emigui/src/containers/resize.rs index 9ab44a83..0e2b6bb0 100644 --- a/emigui/src/containers/resize.rs +++ b/emigui/src/containers/resize.rs @@ -74,6 +74,14 @@ impl Resize { self } + pub fn auto_sized(self) -> Self { + self.resizable(false) + .auto_shrink_width(true) + .auto_expand_width(true) + .auto_shrink_height(true) + .auto_expand_height(true) + } + pub fn fixed_size(mut self, size: Vec2) -> Self { self.auto_shrink_width = false; self.auto_shrink_height = false; diff --git a/emigui/src/context.rs b/emigui/src/context.rs index 0d3c00d2..15f718ac 100644 --- a/emigui/src/context.rs +++ b/emigui/src/context.rs @@ -119,6 +119,7 @@ impl Context { *self.new_fonts.lock() = Some(Arc::new(fonts)); } + // TODO: return MutexGuard pub fn style(&self) -> Style { *self.style.lock() } @@ -211,7 +212,22 @@ impl Context { /// A `Ui` for the entire screen, behind any windows. pub fn fullscreen_ui(self: &Arc) -> Ui { let rect = Rect::from_min_size(Default::default(), self.input().screen_size); - Ui::new(self.clone(), Layer::Background, Id::background(), rect) + let id = Id::background(); + let layer = Layer { + order: Order::Background, + id, + }; + // Ensure we register the background area so it is painted: + self.memory().set_area_state( + layer, + containers::area::State { + pos: rect.min, + size: rect.size(), + interactable: true, + vel: Default::default(), + }, + ); + Ui::new(self.clone(), layer, id, rect) } // --------------------------------------------------------------------- @@ -260,7 +276,7 @@ impl Context { pub fn contains_mouse(&self, layer: Layer, clip_rect: Rect, rect: Rect) -> bool { let rect = rect.intersect(clip_rect); if let Some(mouse_pos) = self.input.mouse_pos { - rect.contains(mouse_pos) && layer == self.memory().layer_at(mouse_pos) + rect.contains(mouse_pos) && self.memory().layer_at(mouse_pos) == Some(layer) } else { false } @@ -333,7 +349,7 @@ impl Context { pub fn show_error(&self, pos: Pos2, text: &str) { let align = (Align::Min, Align::Min); - let layer = Layer::Debug; + let layer = Layer::debug(); let text_style = TextStyle::Monospace; let font = &self.fonts[text_style]; let (text, size) = font.layout_multiline(text, f32::INFINITY); @@ -351,7 +367,7 @@ impl Context { } pub fn debug_text(&self, pos: Pos2, text: &str) { - let layer = Layer::Debug; + let layer = Layer::debug(); let align = (Align::Min, Align::Min); self.floating_text( layer, @@ -364,7 +380,7 @@ impl Context { } pub fn debug_rect(&self, rect: Rect, text: &str) { - let layer = Layer::Debug; + let layer = Layer::debug(); self.add_paint_cmd( layer, PaintCmd::Rect { diff --git a/emigui/src/id.rs b/emigui/src/id.rs index 3951d09c..9d2df3b6 100644 --- a/emigui/src/id.rs +++ b/emigui/src/id.rs @@ -41,7 +41,7 @@ impl Id { Self(0) } - pub fn popup() -> Self { + pub fn tooltip() -> Self { Self(1) } diff --git a/emigui/src/layers.rs b/emigui/src/layers.rs index 69588e41..2867be63 100644 --- a/emigui/src/layers.rs +++ b/emigui/src/layers.rs @@ -1,51 +1,67 @@ use std::collections::HashMap; +use serde_derive::{Deserialize, Serialize}; + use crate::{math::Rect, Id, PaintCmd}; -// TODO: support multiple windows -#[derive(Clone, Copy, Eq, PartialEq, Hash)] -pub enum Layer { +/// Different layer categories +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)] +pub enum Order { + /// Painted behind all floating windows Background, - Window(Id), - /// Tooltips etc - Popup, - /// Debug text + /// Normal moveable windows that you reorder by click + Middle, + /// Popups, menus etc that should always be painted on top of windows + Foreground, + /// Debug layer, always painted last / on top Debug, } +/// An ideintifer for a paint layer. +/// Also acts as an identifier for `Area`:s. +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Deserialize, Serialize)] +pub struct Layer { + pub order: Order, + pub id: Id, +} + +impl Layer { + pub fn debug() -> Self { + Self { + order: Order::Debug, + id: Id::new("debug"), + } + } +} + /// Each `PaintCmd` is paired with a clip rectangle. type PaintList = Vec<(Rect, PaintCmd)>; /// TODO: improve this #[derive(Clone, Default)] -pub struct GraphicLayers { - bg: PaintList, - windows: HashMap, - popup: PaintList, - debug: PaintList, -} +pub struct GraphicLayers(HashMap); impl GraphicLayers { pub fn layer(&mut self, layer: Layer) -> &mut PaintList { - match layer { - Layer::Background => &mut self.bg, - Layer::Window(id) => self.windows.entry(id).or_default(), - Layer::Popup => &mut self.popup, - Layer::Debug => &mut self.debug, - } + self.0.entry(layer).or_default() } - pub fn drain(&mut self, area_order: &[Id]) -> impl ExactSizeIterator { - let mut all_commands: Vec<_> = self.bg.drain(..).collect(); + pub fn drain( + &mut self, + area_order: &[Layer], + ) -> impl ExactSizeIterator { + let mut all_commands: Vec<_> = Default::default(); - for id in area_order { - if let Some(window) = self.windows.get_mut(id) { - all_commands.extend(window.drain(..)); + for layer in area_order { + if let Some(commands) = self.0.get_mut(layer) { + all_commands.extend(commands.drain(..)); } } - all_commands.extend(self.popup.drain(..)); - all_commands.extend(self.debug.drain(..)); + if let Some(commands) = self.0.get_mut(&Layer::debug()) { + all_commands.extend(commands.drain(..)); + } + all_commands.into_iter() } } diff --git a/emigui/src/layout.rs b/emigui/src/layout.rs index 424ece86..e24b8a23 100644 --- a/emigui/src/layout.rs +++ b/emigui/src/layout.rs @@ -26,10 +26,7 @@ impl GuiResponse { /// Show some stuff if the item was hovered pub fn tooltip(&mut self, add_contents: impl FnOnce(&mut Ui)) -> &mut Self { if self.hovered { - if let Some(mouse_pos) = self.ctx.input().mouse_pos { - let window_pos = mouse_pos + vec2(16.0, 16.0); - show_popup(&self.ctx, window_pos, add_contents); - } + show_tooltip(&self.ctx, add_contents); } self } @@ -96,39 +93,26 @@ pub fn align_rect(rect: Rect, align: (Align, Align)) -> Rect { // ---------------------------------------------------------------------------- -// TODO: move show_popup, and expand its features (default size, autosize, etc) -/// Show a pop-over window -pub fn show_popup(ctx: &Arc, window_pos: Pos2, add_contents: impl FnOnce(&mut Ui)) { - let layer = Layer::Popup; - let where_to_put_background = ctx.graphics().layer(layer).len(); - - let style = ctx.style(); - let window_padding = style.window_padding; - - let size = vec2(ctx.input().screen_size.x.min(350.0), f32::INFINITY); // TODO: popup/tooltip width - let inner_rect = Rect::from_min_size(window_pos + window_padding, size); - let mut contents_ui = Ui::new(ctx.clone(), layer, Id::popup(), inner_rect); - - add_contents(&mut contents_ui); - - // Now insert popup background: - - let inner_size = contents_ui.bounding_size(); - let outer_size = inner_size + 2.0 * window_padding; - - let rect = Rect::from_min_size(window_pos, outer_size); - - let mut graphics = ctx.graphics(); - graphics.layer(layer).insert( - where_to_put_background, - ( - Rect::everything(), - PaintCmd::Rect { - corner_radius: 5.0, - fill_color: Some(style.background_fill_color()), - outline: Some(Outline::new(1.0, color::WHITE)), - rect, - }, - ), - ); +pub fn show_tooltip(ctx: &Arc, add_contents: impl FnOnce(&mut Ui)) { + if let Some(mouse_pos) = ctx.input().mouse_pos { + // TODO: default size + let id = Id::tooltip(); + let window_pos = mouse_pos + vec2(16.0, 16.0); + show_popup(ctx, id, window_pos, add_contents); + } +} + +/// Show a pop-over window +pub fn show_popup( + ctx: &Arc, + id: Id, + window_pos: Pos2, + add_contents: impl FnOnce(&mut Ui), +) -> InteractInfo { + use containers::*; + Area::new(id) + .order(Order::Foreground) + .fixed_pos(window_pos) + .interactable(false) + .show(ctx, |ui| Frame::popup(&ctx.style()).show(ui, add_contents)) } diff --git a/emigui/src/memory.rs b/emigui/src/memory.rs index 4d797f93..8fce358d 100644 --- a/emigui/src/memory.rs +++ b/emigui/src/memory.rs @@ -23,9 +23,9 @@ pub struct Memory { area: HashMap, /// Top is last - area_order: Vec, - area_visible_last_frame: HashSet, - area_visible_current_frame: HashSet, + area_order: Vec, + area_visible_last_frame: HashSet, + area_visible_current_frame: HashSet, } impl Memory { @@ -33,44 +33,53 @@ impl Memory { self.area.get(&id).cloned() } - pub(crate) fn area_order(&self) -> &[Id] { + pub(crate) fn area_order(&self) -> &[Layer] { &self.area_order } - pub(crate) fn set_area_state(&mut self, id: Id, state: area::State) { - self.area_visible_current_frame.insert(id); - let did_insert = self.area.insert(id, state).is_none(); + pub(crate) fn set_area_state(&mut self, layer: Layer, state: area::State) { + self.area_visible_current_frame.insert(layer); + let did_insert = self.area.insert(layer.id, state).is_none(); if did_insert { - self.area_order.push(id); + self.area_order.push(layer); + self.area_order.sort_by_key(|layer| layer.order); } } /// TODO: call once at the start of the frame for the current mouse pos - pub fn layer_at(&self, pos: Pos2) -> Layer { - for area_id in self.area_order.iter().rev() { - if self.area_visible_last_frame.contains(area_id) - || self.area_visible_current_frame.contains(area_id) - { - if let Some(state) = self.area.get(area_id) { - let rect = Rect::from_min_size(state.pos, state.size); - if rect.contains(pos) { - return Layer::Window(*area_id); + pub fn layer_at(&self, pos: Pos2) -> Option { + for layer in self.area_order.iter().rev() { + if self.is_area_visible(layer) { + if let Some(state) = self.area.get(&layer.id) { + if state.interactable { + let rect = Rect::from_min_size(state.pos, state.size); + if rect.contains(pos) { + return Some(*layer); + } } } } } - Layer::Background + None } - pub fn move_area_to_top(&mut self, id: Id) { - if self.area_order.last() == Some(&id) { + pub fn is_area_visible(&self, layer: &Layer) -> bool { + self.area_visible_last_frame.contains(layer) + || self.area_visible_current_frame.contains(layer) + } + + pub fn move_area_to_top(&mut self, layer: Layer) { + self.area_visible_current_frame.insert(layer); + + if self.area_order.last() == Some(&layer) { return; // common case early-out } - if let Some(index) = self.area_order.iter().position(|x| *x == id) { + if let Some(index) = self.area_order.iter().position(|x| *x == layer) { self.area_order.remove(index); } - self.area_order.push(id); - self.area_visible_current_frame.insert(id); + self.area_order.push(layer); + + self.area_order.sort_by_key(|layer| layer.order); } pub(crate) fn begin_frame(&mut self) { diff --git a/emigui/src/texture_atlas.rs b/emigui/src/texture_atlas.rs index 2de69a4b..4707dafd 100644 --- a/emigui/src/texture_atlas.rs +++ b/emigui/src/texture_atlas.rs @@ -92,7 +92,7 @@ impl TextureAtlas { impl Texture { pub fn ui(&self, ui: &mut crate::Ui) { - use crate::{color::WHITE, label, layout::show_popup, math::*, Mesh, PaintCmd, Vertex}; + use crate::{color::WHITE, label, layout::show_tooltip, math::*, Mesh, PaintCmd, Vertex}; ui.add(label!( "Texture size: {} x {} (hover to zoom)", @@ -119,35 +119,31 @@ impl Texture { mesh.add_rect(top_left, bottom_right); ui.add_paint_cmd(PaintCmd::Mesh(mesh)); - if let Some(mouse_pos) = ui.input().mouse_pos { - if interact.hovered { - show_popup(ui.ctx(), mouse_pos, |ui| { - let zoom_rect = ui.reserve_space(vec2(128.0, 128.0), None).rect; - let u = remap_clamp(mouse_pos.x, rect.range_x(), 0.0..=self.width as f32 - 1.0) - .round(); - let v = - remap_clamp(mouse_pos.y, rect.range_y(), 0.0..=self.height as f32 - 1.0) - .round(); + if interact.hovered { + show_tooltip(ui.ctx(), |ui| { + let pos = ui.top_left(); + let zoom_rect = ui.reserve_space(vec2(128.0, 128.0), None).rect; + 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(); - let texel_radius = 32.0; - let u = clamp(u, texel_radius..=self.width as f32 - 1.0 - texel_radius); - let v = clamp(v, texel_radius..=self.height as f32 - 1.0 - texel_radius); + let texel_radius = 32.0; + let u = clamp(u, texel_radius..=self.width as f32 - 1.0 - texel_radius); + let v = clamp(v, texel_radius..=self.height as f32 - 1.0 - texel_radius); - let top_left = Vertex { - pos: zoom_rect.min, - uv: ((u - texel_radius) as u16, (v - texel_radius) as u16), - color: WHITE, - }; - let bottom_right = Vertex { - pos: zoom_rect.max, - uv: ((u + texel_radius) as u16, (v + texel_radius) as u16), - color: WHITE, - }; - let mut mesh = Mesh::default(); - mesh.add_rect(top_left, bottom_right); - ui.add_paint_cmd(PaintCmd::Mesh(mesh)); - }); - } + let top_left = Vertex { + pos: zoom_rect.min, + uv: ((u - texel_radius) as u16, (v - texel_radius) as u16), + color: WHITE, + }; + let bottom_right = Vertex { + pos: zoom_rect.max, + uv: ((u + texel_radius) as u16, (v + texel_radius) as u16), + color: WHITE, + }; + let mut mesh = Mesh::default(); + mesh.add_rect(top_left, bottom_right); + ui.add_paint_cmd(PaintCmd::Mesh(mesh)); + }); } } }