Add Floating and Frame containers as building blocks for windows
This commit is contained in:
parent
3a430c8fc7
commit
e26d08851f
8 changed files with 212 additions and 17 deletions
|
@ -41,6 +41,13 @@ Add extremely quick animations for some things, maybe 2-3 frames. For instance:
|
|||
* [ ] Use clip rectangles when interacting
|
||||
* [x] Adjust clip rects so edges of child widgets aren't clipped
|
||||
|
||||
### Modularity
|
||||
* [x] `trait Widget` (`Label`, `Slider`, `Checkbox`, ...)
|
||||
* [ ] `trait Container` (`Frame`, `Resize`, `ScrollArea`, ...)
|
||||
* [ ] `widget::TextButton` implemented as a `container::Button` which contains a `widget::Label`.
|
||||
* [ ] Easily chain `Container`s without nested closures.
|
||||
* e.g. `region.containers((Frame::new(), Resize::new(), ScrollArea::new()), |ui| ...)`
|
||||
|
||||
### Other
|
||||
* [ ] Generalize Layout so we can create grid layouts etc
|
||||
* [ ] Persist UI state in external storage
|
||||
|
@ -56,6 +63,7 @@ Add extremely quick animations for some things, maybe 2-3 frames. For instance:
|
|||
* `region: &Region` `region.add(...)` :/
|
||||
* `gui: &Gui` `gui.add(...)` :)
|
||||
* `ui: &Ui` `ui.add(...)` :)
|
||||
* [ ] Maybe find a shorter name for the library like `egui`?
|
||||
|
||||
### Global widget search
|
||||
Ability to do a search for any widget. The search works even for collapsed regions and closed windows and menus. This is implemented like this: while searching, all region are layed out and their add_content functions are run. If none of the contents matches the search, the layout is reverted and nothing is shown. So windows will get temporarily opened and run, but if the search is not a match in the window it is closed again. This means then when searching your whole GUI is being run, which may be a bit slower, but it would be a really awesome feature.
|
||||
|
|
|
@ -86,15 +86,15 @@ impl Context {
|
|||
|
||||
/// Generate a id from the given source.
|
||||
/// If it is not unique, an error will be printed at the given position.
|
||||
pub fn make_unique_id<IdSource>(&self, source: &IdSource, pos: Pos2) -> Id
|
||||
pub fn make_unique_id<IdSource>(&self, source: IdSource, pos: Pos2) -> Id
|
||||
where
|
||||
IdSource: std::hash::Hash + std::fmt::Debug,
|
||||
IdSource: std::hash::Hash + std::fmt::Debug + Copy,
|
||||
{
|
||||
self.register_unique_id(Id::new(source), source, pos)
|
||||
}
|
||||
|
||||
/// If the given Id is not unique, an error will be printed at the given position.
|
||||
pub fn register_unique_id(&self, id: Id, source_name: &impl std::fmt::Debug, pos: Pos2) -> Id {
|
||||
pub fn register_unique_id(&self, id: Id, source_name: impl std::fmt::Debug, pos: Pos2) -> Id {
|
||||
if let Some(clash_pos) = self.used_ids.lock().insert(id, pos) {
|
||||
if clash_pos.dist(pos) < 4.0 {
|
||||
self.show_error(
|
||||
|
|
103
emigui/src/floating.rs
Normal file
103
emigui/src/floating.rs
Normal file
|
@ -0,0 +1,103 @@
|
|||
//! A Floating is a region that has no parent, it floats on the background.
|
||||
//! It is potentioally movable.
|
||||
//! It has no frame or own size.
|
||||
//! It is the foundation for a window
|
||||
//!
|
||||
use std::{fmt::Debug, hash::Hash, sync::Arc};
|
||||
|
||||
use crate::*;
|
||||
|
||||
// struct State {
|
||||
// pos: Pos2,
|
||||
// /// used for catching clickes
|
||||
// size: Vec2,
|
||||
// }
|
||||
|
||||
type State = crate::window::State;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Floating {
|
||||
movable: bool,
|
||||
default_pos: Option<Pos2>,
|
||||
}
|
||||
|
||||
impl Floating {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
movable: true,
|
||||
default_pos: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn movable(mut self, movable: bool) -> Self {
|
||||
self.movable = movable;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Floating {
|
||||
pub fn show(
|
||||
self,
|
||||
ctx: &Arc<Context>,
|
||||
id_source: impl Copy + Debug + Hash,
|
||||
add_contents: impl FnOnce(&mut Region),
|
||||
) {
|
||||
let default_pos = self.default_pos.unwrap_or_else(|| pos2(100.0, 100.0)); // TODO
|
||||
let id = ctx.make_unique_id(id_source, default_pos);
|
||||
let layer = Layer::Window(id);
|
||||
|
||||
let (mut state, _is_new) = match ctx.memory.lock().get_window(id) {
|
||||
Some(state) => (state, false),
|
||||
None => {
|
||||
let state = State {
|
||||
outer_pos: default_pos,
|
||||
inner_size: Vec2::zero(),
|
||||
outer_rect: Rect::from_min_size(default_pos, Vec2::zero()),
|
||||
};
|
||||
(state, true)
|
||||
}
|
||||
};
|
||||
state.outer_pos = state.outer_pos.round();
|
||||
|
||||
let mut region = Region::new(
|
||||
ctx.clone(),
|
||||
layer,
|
||||
id,
|
||||
Rect::from_min_size(state.outer_pos, Vec2::infinity()),
|
||||
);
|
||||
add_contents(&mut region);
|
||||
let size = region.bounding_size.ceil();
|
||||
|
||||
state.outer_rect = Rect::from_min_size(state.outer_pos, size);
|
||||
let move_interact = ctx.interact(layer, &state.outer_rect, Some(id.with("move")));
|
||||
|
||||
if move_interact.active {
|
||||
state.outer_pos += ctx.input().mouse_move;
|
||||
}
|
||||
|
||||
// Constrain to screen:
|
||||
let margin = 32.0;
|
||||
state.outer_pos = state.outer_pos.max(pos2(margin - size.x, 0.0));
|
||||
state.outer_pos = state.outer_pos.min(pos2(
|
||||
ctx.input.screen_size.x - margin,
|
||||
ctx.input.screen_size.y - margin,
|
||||
));
|
||||
state.outer_pos = state.outer_pos.round();
|
||||
|
||||
state.inner_size = size;
|
||||
state.outer_rect = Rect::from_min_size(state.outer_pos, size);
|
||||
|
||||
if move_interact.active || mouse_pressed_on_window(ctx, id) {
|
||||
ctx.memory.lock().move_window_to_top(id);
|
||||
}
|
||||
ctx.memory.lock().set_window_state(id, state);
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_pressed_on_window(ctx: &Context, id: Id) -> bool {
|
||||
if let Some(mouse_pos) = ctx.input.mouse_pos {
|
||||
ctx.input.mouse_pressed && ctx.memory.lock().layer_at(mouse_pos) == Layer::Window(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
41
emigui/src/frame.rs
Normal file
41
emigui/src/frame.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
//! Frame container
|
||||
//!
|
||||
use crate::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Frame {}
|
||||
|
||||
impl Frame {
|
||||
pub fn show(self, region: &mut Region, add_contents: impl FnOnce(&mut Region)) {
|
||||
let style = region.style();
|
||||
let margin = style.window_padding;
|
||||
|
||||
let outer_pos = region.cursor();
|
||||
let inner_rect =
|
||||
Rect::from_min_size(outer_pos + margin, region.available_space() - 2.0 * margin);
|
||||
let where_to_put_background = region.paint_list_len();
|
||||
|
||||
let mut child_region = region.child_region(inner_rect);
|
||||
add_contents(&mut child_region);
|
||||
|
||||
// TODO: handle the last item_spacing in a nicer way
|
||||
let inner_size = child_region.bounding_size;
|
||||
let inner_size = inner_size.ceil(); // TODO: round to pixel
|
||||
|
||||
let outer_rect = Rect::from_min_size(outer_pos, margin + inner_size + margin);
|
||||
|
||||
let corner_radius = style.window.corner_radius;
|
||||
let fill_color = style.background_fill_color();
|
||||
region.insert_paint_cmd(
|
||||
where_to_put_background,
|
||||
PaintCmd::Rect {
|
||||
corner_radius,
|
||||
fill_color: Some(fill_color),
|
||||
outline: Some(Outline::new(1.0, color::WHITE)),
|
||||
rect: outer_rect,
|
||||
},
|
||||
);
|
||||
|
||||
region.bounding_size = margin + child_region.bounding_size + margin; // TODO: this is not really right
|
||||
}
|
||||
}
|
|
@ -11,8 +11,10 @@ pub mod color;
|
|||
mod context;
|
||||
mod emigui;
|
||||
pub mod example_app;
|
||||
mod floating;
|
||||
mod font;
|
||||
mod fonts;
|
||||
mod frame;
|
||||
mod id;
|
||||
mod layers;
|
||||
mod layout;
|
||||
|
@ -33,7 +35,9 @@ pub use {
|
|||
collapsing_header::CollapsingHeader,
|
||||
color::Color,
|
||||
context::Context,
|
||||
floating::Floating,
|
||||
fonts::{FontDefinitions, Fonts, TextStyle},
|
||||
frame::Frame,
|
||||
id::Id,
|
||||
layers::*,
|
||||
layout::{Align, GuiResponse},
|
||||
|
|
|
@ -53,6 +53,9 @@ pub struct Region {
|
|||
pub(crate) cursor: Pos2,
|
||||
}
|
||||
|
||||
// Allow child widgets to be just on the border and still have an outline with some thickness
|
||||
const CLIP_RECT_MARGIN: f32 = 3.0;
|
||||
|
||||
impl Region {
|
||||
pub fn new(ctx: Arc<Context>, layer: Layer, id: Id, rect: Rect) -> Self {
|
||||
let style = ctx.style();
|
||||
|
@ -60,7 +63,7 @@ impl Region {
|
|||
ctx,
|
||||
id,
|
||||
layer,
|
||||
clip_rect: rect,
|
||||
clip_rect: rect.expand(CLIP_RECT_MARGIN),
|
||||
desired_rect: rect,
|
||||
bounding_size: Vec2::default(),
|
||||
style,
|
||||
|
@ -71,16 +74,15 @@ impl Region {
|
|||
}
|
||||
|
||||
pub fn child_region(&self, child_rect: Rect) -> Self {
|
||||
// Allow child widgets to be just on the border and still have an outline with some thickness
|
||||
const CLIP_RECT_MARGIN: f32 = 3.0;
|
||||
let clip_rect = self
|
||||
.clip_rect
|
||||
.intersect(child_rect.expand(CLIP_RECT_MARGIN));
|
||||
Region {
|
||||
ctx: self.ctx.clone(),
|
||||
layer: self.layer,
|
||||
style: self.style,
|
||||
id: self.id,
|
||||
clip_rect: self
|
||||
.clip_rect
|
||||
.intersect(child_rect.expand(CLIP_RECT_MARGIN)),
|
||||
clip_rect,
|
||||
desired_rect: child_rect,
|
||||
cursor: child_rect.min(),
|
||||
bounding_size: vec2(0.0, 0.0),
|
||||
|
@ -109,6 +111,19 @@ impl Region {
|
|||
.extend(cmds.drain(..).map(|cmd| (clip_rect, cmd)));
|
||||
}
|
||||
|
||||
/// Insert a paint cmd before existing ones
|
||||
pub fn insert_paint_cmd(&mut self, pos: usize, paint_cmd: PaintCmd) {
|
||||
self.ctx
|
||||
.graphics
|
||||
.lock()
|
||||
.layer(self.layer)
|
||||
.insert(pos, (self.clip_rect(), paint_cmd));
|
||||
}
|
||||
|
||||
pub fn paint_list_len(&self) -> usize {
|
||||
self.ctx.graphics.lock().layer(self.layer).len()
|
||||
}
|
||||
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
self.ctx.round_to_pixel(point)
|
||||
}
|
||||
|
@ -348,7 +363,11 @@ impl Region {
|
|||
// ------------------------------------------------------------------------
|
||||
|
||||
pub fn reserve_space(&mut self, size: Vec2, interaction_id: Option<Id>) -> InteractInfo {
|
||||
let pos = self.reserve_space_without_padding(size + self.style.item_spacing);
|
||||
let padded_size = match self.dir {
|
||||
Direction::Horizontal => vec2(size.x + self.style.item_spacing.x, size.y),
|
||||
Direction::Vertical => vec2(size.x, size.y + self.style.item_spacing.y),
|
||||
};
|
||||
let pos = self.reserve_space_without_padding(padded_size);
|
||||
let rect = Rect::from_min_size(pos, size);
|
||||
self.ctx.interact(self.layer, &rect, interaction_id)
|
||||
}
|
||||
|
|
|
@ -43,10 +43,23 @@ impl Resize {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn default_size(mut self, default_size: Vec2) -> Self {
|
||||
self.default_size = default_size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn as_wide_as_possible(mut self) -> Self {
|
||||
self.min_size.x = f32::INFINITY;
|
||||
self
|
||||
}
|
||||
|
||||
/// true: prevent from resizing to smaller than contents.
|
||||
/// false: allow shrinking to smaller than contents.
|
||||
pub fn auto_expand(mut self, auto_expand: bool) -> Self {
|
||||
self.expand_width_to_fit_content = auto_expand;
|
||||
self.expand_height_to_fit_content = auto_expand;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: a common trait for Things that follow this pattern
|
||||
|
|
|
@ -3,13 +3,7 @@
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use {
|
||||
emigui::{
|
||||
example_app::ExampleApp,
|
||||
label,
|
||||
math::*,
|
||||
widgets::{Button, Label},
|
||||
Align, CursorIcon, Emigui, Window,
|
||||
},
|
||||
emigui::{example_app::ExampleApp, widgets::*, *},
|
||||
emigui_glium::Painter,
|
||||
glium::glutin,
|
||||
};
|
||||
|
@ -127,6 +121,19 @@ fn main() {
|
|||
emigui.ui(region);
|
||||
});
|
||||
|
||||
Floating::new().show(region.ctx(), "Floating", |region| {
|
||||
Frame::default().show(region, |region| {
|
||||
Resize::default()
|
||||
.default_size(vec2(300.0, 200.0))
|
||||
.auto_expand(true)
|
||||
.show(region, |region| {
|
||||
region.add(Label::new("Fake Window").text_style(TextStyle::Heading));
|
||||
region.add(Separator::new().line_width(1.0)); // TODO: nicer way to split window title from contents
|
||||
region.add(label!("Floating Frame Resize"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let (output, paint_batches) = emigui.end_frame();
|
||||
painter.paint_batches(&display, paint_batches, emigui.texture());
|
||||
|
||||
|
|
Loading…
Reference in a new issue