Refactor layers

This commit is contained in:
Emil Ernerfeldt 2020-05-10 14:27:02 +02:00
parent fd99213222
commit be6ada6923
9 changed files with 189 additions and 134 deletions

View file

@ -14,6 +14,10 @@ pub(crate) struct State {
/// Last know size. Used for catching clicks. /// Last know size. Used for catching clicks.
pub size: Vec2, 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. /// You can throw a moveable Area. It's fun.
/// TODO: separate out moveable to container? /// TODO: separate out moveable to container?
#[serde(skip)] #[serde(skip)]
@ -24,7 +28,8 @@ pub(crate) struct State {
pub struct Area { pub struct Area {
id: Id, id: Id,
movable: bool, movable: bool,
always_on_top: bool, interactable: bool,
order: Order,
default_pos: Option<Pos2>, default_pos: Option<Pos2>,
fixed_pos: Option<Pos2>, fixed_pos: Option<Pos2>,
} }
@ -34,7 +39,8 @@ impl Area {
Self { Self {
id: Id::new(id_source), id: Id::new(id_source),
movable: true, movable: true,
always_on_top: false, interactable: true,
order: Order::Middle,
default_pos: None, default_pos: None,
fixed_pos: None, fixed_pos: None,
} }
@ -42,12 +48,21 @@ impl Area {
pub fn movable(mut self, movable: bool) -> Self { pub fn movable(mut self, movable: bool) -> Self {
self.movable = movable; self.movable = movable;
self.interactable |= movable;
self self
} }
/// Always show as top Area /// If false, clicks goes stright throught to what is behind us.
pub fn always_on_top(mut self) -> Self { /// Good for tooltips etc.
self.always_on_top = true; 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 self
} }
@ -74,14 +89,15 @@ impl Area {
let Area { let Area {
id, id,
movable, movable,
always_on_top, order,
interactable,
default_pos, default_pos,
fixed_pos, fixed_pos,
} = self; } = self;
let default_pos = default_pos.unwrap_or_else(|| pos2(100.0, 100.0)); // TODO 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 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) { let (mut state, _is_new) = match ctx.memory().get_area(id) {
Some(state) => (state, false), Some(state) => (state, false),
@ -89,6 +105,7 @@ impl Area {
let state = State { let state = State {
pos: default_pos, pos: default_pos,
size: Vec2::zero(), size: Vec2::zero(),
interactable,
vel: Vec2::zero(), vel: Vec2::zero(),
}; };
(state, true) (state, true)
@ -144,18 +161,18 @@ impl Area {
// &format!("Area size: {:?}", state.size), // &format!("Area size: {:?}", state.size),
// ); // );
if move_interact.active || mouse_pressed_on_area(ctx, id) || always_on_top { if move_interact.active || mouse_pressed_on_area(ctx, layer) {
ctx.memory().move_area_to_top(id); ctx.memory().move_area_to_top(layer);
} }
ctx.memory().set_area_state(id, state); ctx.memory().set_area_state(layer, state);
move_interact 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 { 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 { } else {
false false
} }

View file

@ -28,6 +28,15 @@ impl Frame {
outline: Some(Outline::new(1.0, color::white(128))), 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 { impl Frame {

View file

@ -74,6 +74,14 @@ impl Resize {
self 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 { pub fn fixed_size(mut self, size: Vec2) -> Self {
self.auto_shrink_width = false; self.auto_shrink_width = false;
self.auto_shrink_height = false; self.auto_shrink_height = false;

View file

@ -119,6 +119,7 @@ impl Context {
*self.new_fonts.lock() = Some(Arc::new(fonts)); *self.new_fonts.lock() = Some(Arc::new(fonts));
} }
// TODO: return MutexGuard
pub fn style(&self) -> Style { pub fn style(&self) -> Style {
*self.style.lock() *self.style.lock()
} }
@ -211,7 +212,22 @@ impl Context {
/// A `Ui` for the entire screen, behind any windows. /// A `Ui` for the entire screen, behind any windows.
pub fn fullscreen_ui(self: &Arc<Self>) -> Ui { pub fn fullscreen_ui(self: &Arc<Self>) -> Ui {
let rect = Rect::from_min_size(Default::default(), self.input().screen_size); 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 { pub fn contains_mouse(&self, layer: Layer, clip_rect: Rect, rect: Rect) -> bool {
let rect = rect.intersect(clip_rect); let rect = rect.intersect(clip_rect);
if let Some(mouse_pos) = self.input.mouse_pos { 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 { } else {
false false
} }
@ -333,7 +349,7 @@ impl Context {
pub fn show_error(&self, pos: Pos2, text: &str) { pub fn show_error(&self, pos: Pos2, text: &str) {
let align = (Align::Min, Align::Min); let align = (Align::Min, Align::Min);
let layer = Layer::Debug; let layer = Layer::debug();
let text_style = TextStyle::Monospace; let text_style = TextStyle::Monospace;
let font = &self.fonts[text_style]; let font = &self.fonts[text_style];
let (text, size) = font.layout_multiline(text, f32::INFINITY); let (text, size) = font.layout_multiline(text, f32::INFINITY);
@ -351,7 +367,7 @@ impl Context {
} }
pub fn debug_text(&self, pos: Pos2, text: &str) { pub fn debug_text(&self, pos: Pos2, text: &str) {
let layer = Layer::Debug; let layer = Layer::debug();
let align = (Align::Min, Align::Min); let align = (Align::Min, Align::Min);
self.floating_text( self.floating_text(
layer, layer,
@ -364,7 +380,7 @@ impl Context {
} }
pub fn debug_rect(&self, rect: Rect, text: &str) { pub fn debug_rect(&self, rect: Rect, text: &str) {
let layer = Layer::Debug; let layer = Layer::debug();
self.add_paint_cmd( self.add_paint_cmd(
layer, layer,
PaintCmd::Rect { PaintCmd::Rect {

View file

@ -41,7 +41,7 @@ impl Id {
Self(0) Self(0)
} }
pub fn popup() -> Self { pub fn tooltip() -> Self {
Self(1) Self(1)
} }

View file

@ -1,51 +1,67 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde_derive::{Deserialize, Serialize};
use crate::{math::Rect, Id, PaintCmd}; use crate::{math::Rect, Id, PaintCmd};
// TODO: support multiple windows /// Different layer categories
#[derive(Clone, Copy, Eq, PartialEq, Hash)] #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)]
pub enum Layer { pub enum Order {
/// Painted behind all floating windows
Background, Background,
Window(Id), /// Normal moveable windows that you reorder by click
/// Tooltips etc Middle,
Popup, /// Popups, menus etc that should always be painted on top of windows
/// Debug text Foreground,
/// Debug layer, always painted last / on top
Debug, 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. /// Each `PaintCmd` is paired with a clip rectangle.
type PaintList = Vec<(Rect, PaintCmd)>; type PaintList = Vec<(Rect, PaintCmd)>;
/// TODO: improve this /// TODO: improve this
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct GraphicLayers { pub struct GraphicLayers(HashMap<Layer, PaintList>);
bg: PaintList,
windows: HashMap<Id, PaintList>,
popup: PaintList,
debug: PaintList,
}
impl GraphicLayers { impl GraphicLayers {
pub fn layer(&mut self, layer: Layer) -> &mut PaintList { pub fn layer(&mut self, layer: Layer) -> &mut PaintList {
match layer { self.0.entry(layer).or_default()
Layer::Background => &mut self.bg, }
Layer::Window(id) => self.windows.entry(id).or_default(),
Layer::Popup => &mut self.popup, pub fn drain(
Layer::Debug => &mut self.debug, &mut self,
area_order: &[Layer],
) -> impl ExactSizeIterator<Item = (Rect, PaintCmd)> {
let mut all_commands: Vec<_> = Default::default();
for layer in area_order {
if let Some(commands) = self.0.get_mut(layer) {
all_commands.extend(commands.drain(..));
} }
} }
pub fn drain(&mut self, area_order: &[Id]) -> impl ExactSizeIterator<Item = (Rect, PaintCmd)> { if let Some(commands) = self.0.get_mut(&Layer::debug()) {
let mut all_commands: Vec<_> = self.bg.drain(..).collect(); all_commands.extend(commands.drain(..));
for id in area_order {
if let Some(window) = self.windows.get_mut(id) {
all_commands.extend(window.drain(..));
}
} }
all_commands.extend(self.popup.drain(..));
all_commands.extend(self.debug.drain(..));
all_commands.into_iter() all_commands.into_iter()
} }
} }

View file

@ -26,10 +26,7 @@ impl GuiResponse {
/// Show some stuff if the item was hovered /// Show some stuff if the item was hovered
pub fn tooltip(&mut self, add_contents: impl FnOnce(&mut Ui)) -> &mut Self { pub fn tooltip(&mut self, add_contents: impl FnOnce(&mut Ui)) -> &mut Self {
if self.hovered { if self.hovered {
if let Some(mouse_pos) = self.ctx.input().mouse_pos { show_tooltip(&self.ctx, add_contents);
let window_pos = mouse_pos + vec2(16.0, 16.0);
show_popup(&self.ctx, window_pos, add_contents);
}
} }
self 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) pub fn show_tooltip(ctx: &Arc<Context>, add_contents: impl FnOnce(&mut Ui)) {
/// Show a pop-over window if let Some(mouse_pos) = ctx.input().mouse_pos {
pub fn show_popup(ctx: &Arc<Context>, window_pos: Pos2, add_contents: impl FnOnce(&mut Ui)) { // TODO: default size
let layer = Layer::Popup; let id = Id::tooltip();
let where_to_put_background = ctx.graphics().layer(layer).len(); let window_pos = mouse_pos + vec2(16.0, 16.0);
show_popup(ctx, id, window_pos, add_contents);
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 /// Show a pop-over window
let inner_rect = Rect::from_min_size(window_pos + window_padding, size); pub fn show_popup(
let mut contents_ui = Ui::new(ctx.clone(), layer, Id::popup(), inner_rect); ctx: &Arc<Context>,
id: Id,
add_contents(&mut contents_ui); window_pos: Pos2,
add_contents: impl FnOnce(&mut Ui),
// Now insert popup background: ) -> InteractInfo {
use containers::*;
let inner_size = contents_ui.bounding_size(); Area::new(id)
let outer_size = inner_size + 2.0 * window_padding; .order(Order::Foreground)
.fixed_pos(window_pos)
let rect = Rect::from_min_size(window_pos, outer_size); .interactable(false)
.show(ctx, |ui| Frame::popup(&ctx.style()).show(ui, add_contents))
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,
},
),
);
} }

View file

@ -23,9 +23,9 @@ pub struct Memory {
area: HashMap<Id, area::State>, area: HashMap<Id, area::State>,
/// Top is last /// Top is last
area_order: Vec<Id>, area_order: Vec<Layer>,
area_visible_last_frame: HashSet<Id>, area_visible_last_frame: HashSet<Layer>,
area_visible_current_frame: HashSet<Id>, area_visible_current_frame: HashSet<Layer>,
} }
impl Memory { impl Memory {
@ -33,44 +33,53 @@ impl Memory {
self.area.get(&id).cloned() self.area.get(&id).cloned()
} }
pub(crate) fn area_order(&self) -> &[Id] { pub(crate) fn area_order(&self) -> &[Layer] {
&self.area_order &self.area_order
} }
pub(crate) fn set_area_state(&mut self, id: Id, state: area::State) { pub(crate) fn set_area_state(&mut self, layer: Layer, state: area::State) {
self.area_visible_current_frame.insert(id); self.area_visible_current_frame.insert(layer);
let did_insert = self.area.insert(id, state).is_none(); let did_insert = self.area.insert(layer.id, state).is_none();
if did_insert { 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 /// TODO: call once at the start of the frame for the current mouse pos
pub fn layer_at(&self, pos: Pos2) -> Layer { pub fn layer_at(&self, pos: Pos2) -> Option<Layer> {
for area_id in self.area_order.iter().rev() { for layer in self.area_order.iter().rev() {
if self.area_visible_last_frame.contains(area_id) if self.is_area_visible(layer) {
|| self.area_visible_current_frame.contains(area_id) if let Some(state) = self.area.get(&layer.id) {
{ if state.interactable {
if let Some(state) = self.area.get(area_id) {
let rect = Rect::from_min_size(state.pos, state.size); let rect = Rect::from_min_size(state.pos, state.size);
if rect.contains(pos) { if rect.contains(pos) {
return Layer::Window(*area_id); return Some(*layer);
} }
} }
} }
} }
Layer::Background }
None
} }
pub fn move_area_to_top(&mut self, id: Id) { pub fn is_area_visible(&self, layer: &Layer) -> bool {
if self.area_order.last() == Some(&id) { 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 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.remove(index);
} }
self.area_order.push(id); self.area_order.push(layer);
self.area_visible_current_frame.insert(id);
self.area_order.sort_by_key(|layer| layer.order);
} }
pub(crate) fn begin_frame(&mut self) { pub(crate) fn begin_frame(&mut self) {

View file

@ -92,7 +92,7 @@ impl TextureAtlas {
impl Texture { impl Texture {
pub fn ui(&self, ui: &mut crate::Ui) { 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!( ui.add(label!(
"Texture size: {} x {} (hover to zoom)", "Texture size: {} x {} (hover to zoom)",
@ -119,15 +119,12 @@ impl Texture {
mesh.add_rect(top_left, bottom_right); mesh.add_rect(top_left, bottom_right);
ui.add_paint_cmd(PaintCmd::Mesh(mesh)); ui.add_paint_cmd(PaintCmd::Mesh(mesh));
if let Some(mouse_pos) = ui.input().mouse_pos {
if interact.hovered { if interact.hovered {
show_popup(ui.ctx(), mouse_pos, |ui| { show_tooltip(ui.ctx(), |ui| {
let pos = ui.top_left();
let zoom_rect = ui.reserve_space(vec2(128.0, 128.0), None).rect; 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) let u = remap_clamp(pos.x, rect.range_x(), 0.0..=self.width as f32 - 1.0).round();
.round(); let v = remap_clamp(pos.y, rect.range_y(), 0.0..=self.height as f32 - 1.0).round();
let v =
remap_clamp(mouse_pos.y, rect.range_y(), 0.0..=self.height as f32 - 1.0)
.round();
let texel_radius = 32.0; let texel_radius = 32.0;
let u = clamp(u, texel_radius..=self.width as f32 - 1.0 - texel_radius); let u = clamp(u, texel_radius..=self.width as f32 - 1.0 - texel_radius);
@ -149,5 +146,4 @@ impl Texture {
}); });
} }
} }
}
} }