Refactor layers
This commit is contained in:
parent
fd99213222
commit
be6ada6923
9 changed files with 189 additions and 134 deletions
|
@ -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<Pos2>,
|
||||
fixed_pos: Option<Pos2>,
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Self>) -> 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 {
|
||||
|
|
|
@ -41,7 +41,7 @@ impl Id {
|
|||
Self(0)
|
||||
}
|
||||
|
||||
pub fn popup() -> Self {
|
||||
pub fn tooltip() -> Self {
|
||||
Self(1)
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Id, PaintList>,
|
||||
popup: PaintList,
|
||||
debug: PaintList,
|
||||
}
|
||||
pub struct GraphicLayers(HashMap<Layer, PaintList>);
|
||||
|
||||
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<Item = (Rect, PaintCmd)> {
|
||||
let mut all_commands: Vec<_> = self.bg.drain(..).collect();
|
||||
pub fn drain(
|
||||
&mut self,
|
||||
area_order: &[Layer],
|
||||
) -> impl ExactSizeIterator<Item = (Rect, PaintCmd)> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Context>, 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<Context>, 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<Context>,
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ pub struct Memory {
|
|||
|
||||
area: HashMap<Id, area::State>,
|
||||
/// Top is last
|
||||
area_order: Vec<Id>,
|
||||
area_visible_last_frame: HashSet<Id>,
|
||||
area_visible_current_frame: HashSet<Id>,
|
||||
area_order: Vec<Layer>,
|
||||
area_visible_last_frame: HashSet<Layer>,
|
||||
area_visible_current_frame: HashSet<Layer>,
|
||||
}
|
||||
|
||||
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<Layer> {
|
||||
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) {
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue