Implement Window as collection of Floating + Frame + Resize
This commit is contained in:
parent
649dcec09c
commit
154424384f
16 changed files with 301 additions and 459 deletions
21
emigui/src/containers.rs
Normal file
21
emigui/src/containers.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
pub mod collapsing_header;
|
||||||
|
pub mod floating;
|
||||||
|
pub mod frame;
|
||||||
|
pub mod resize;
|
||||||
|
pub mod scroll_area;
|
||||||
|
pub mod window;
|
||||||
|
|
||||||
|
pub use {
|
||||||
|
collapsing_header::CollapsingHeader, floating::Floating, frame::Frame, resize::Resize,
|
||||||
|
scroll_area::ScrollArea, window::Window,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// pub trait Container {
|
||||||
|
// fn show(self, region: &mut Region, add_contents: impl FnOnce(&mut Region));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub trait Container {
|
||||||
|
// fn begin(&mut self, parent: &mut Region) -> Region;
|
||||||
|
// fn end(self, parent: &mut Region, content: Region);
|
||||||
|
// }
|
104
emigui/src/containers/floating.rs
Normal file
104
emigui/src/containers/floating.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
//! 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::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct State {
|
||||||
|
/// Last known pos
|
||||||
|
pub pos: Pos2,
|
||||||
|
|
||||||
|
/// Last know size. Used for catching clicks.
|
||||||
|
pub size: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: rename Floating to something else.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Floating {
|
||||||
|
id: Id,
|
||||||
|
movable: bool,
|
||||||
|
default_pos: Option<Pos2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Floating {
|
||||||
|
pub fn new(id_source: impl Hash) -> Self {
|
||||||
|
Self {
|
||||||
|
id: Id::new(id_source),
|
||||||
|
movable: true,
|
||||||
|
default_pos: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn movable(mut self, movable: bool) -> Self {
|
||||||
|
self.movable = movable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_pos(mut self, default_pos: Pos2) -> Self {
|
||||||
|
self.default_pos = Some(default_pos);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Floating {
|
||||||
|
pub fn show(self, ctx: &Arc<Context>, add_contents: impl FnOnce(&mut Region)) {
|
||||||
|
let default_pos = self.default_pos.unwrap_or_else(|| pos2(100.0, 100.0)); // TODO
|
||||||
|
let id = ctx.register_unique_id(self.id, "Floating", default_pos);
|
||||||
|
let layer = Layer::Window(id);
|
||||||
|
|
||||||
|
let (mut state, _is_new) = match ctx.memory.lock().get_floating(id) {
|
||||||
|
Some(state) => (state, false),
|
||||||
|
None => {
|
||||||
|
let state = State {
|
||||||
|
pos: default_pos,
|
||||||
|
size: Vec2::zero(),
|
||||||
|
};
|
||||||
|
(state, true)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.pos = state.pos.round();
|
||||||
|
|
||||||
|
let mut region = Region::new(
|
||||||
|
ctx.clone(),
|
||||||
|
layer,
|
||||||
|
id,
|
||||||
|
Rect::from_min_size(state.pos, Vec2::infinity()),
|
||||||
|
);
|
||||||
|
add_contents(&mut region);
|
||||||
|
state.size = region.bounding_size().ceil();
|
||||||
|
|
||||||
|
let rect = Rect::from_min_size(state.pos, state.size);
|
||||||
|
let move_interact = ctx.interact(layer, &rect, Some(id.with("move")));
|
||||||
|
|
||||||
|
if move_interact.active {
|
||||||
|
state.pos += ctx.input().mouse_move;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constrain to screen:
|
||||||
|
let margin = 32.0;
|
||||||
|
state.pos = state.pos.max(pos2(margin - state.size.x, 0.0));
|
||||||
|
state.pos = state.pos.min(pos2(
|
||||||
|
ctx.input.screen_size.x - margin,
|
||||||
|
ctx.input.screen_size.y - margin,
|
||||||
|
));
|
||||||
|
|
||||||
|
state.pos = state.pos.round();
|
||||||
|
|
||||||
|
if move_interact.active || mouse_pressed_on_floating(ctx, id) {
|
||||||
|
ctx.memory.lock().move_floating_to_top(id);
|
||||||
|
}
|
||||||
|
ctx.memory.lock().set_floating_state(id, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_pressed_on_floating(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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
//! Frame container
|
//! Frame container
|
||||||
//!
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Frame {}
|
pub struct Frame {}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
|
@ -8,6 +8,9 @@ pub struct State {
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Resize {
|
pub struct Resize {
|
||||||
|
/// If false, we are no enabled
|
||||||
|
resizable: bool,
|
||||||
|
|
||||||
// Will still try to stay within parent region bounds
|
// Will still try to stay within parent region bounds
|
||||||
min_size: Vec2,
|
min_size: Vec2,
|
||||||
max_size: Vec2,
|
max_size: Vec2,
|
||||||
|
@ -15,24 +18,28 @@ pub struct Resize {
|
||||||
default_size: Vec2,
|
default_size: Vec2,
|
||||||
|
|
||||||
// If true, won't allow you to make window so big that it creates spacing
|
// If true, won't allow you to make window so big that it creates spacing
|
||||||
shrink_width_to_fit_content: bool,
|
auto_shrink_width: bool,
|
||||||
shrink_height_to_fit_content: bool,
|
auto_shrink_height: bool,
|
||||||
|
|
||||||
// If true, won't allow you to resize smaller than that everything fits.
|
// If true, won't allow you to resize smaller than that everything fits.
|
||||||
expand_width_to_fit_content: bool,
|
expand_width_to_fit_content: bool,
|
||||||
expand_height_to_fit_content: bool,
|
expand_height_to_fit_content: bool,
|
||||||
|
|
||||||
|
handle_offset: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Resize {
|
impl Default for Resize {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
resizable: true,
|
||||||
min_size: Vec2::splat(32.0),
|
min_size: Vec2::splat(32.0),
|
||||||
max_size: Vec2::infinity(),
|
max_size: Vec2::infinity(),
|
||||||
default_size: vec2(f32::INFINITY, 200.0), // TODO
|
default_size: vec2(f32::INFINITY, 200.0), // TODO
|
||||||
shrink_width_to_fit_content: false,
|
auto_shrink_width: false,
|
||||||
shrink_height_to_fit_content: false,
|
auto_shrink_height: false,
|
||||||
expand_width_to_fit_content: true,
|
expand_width_to_fit_content: true,
|
||||||
expand_height_to_fit_content: true,
|
expand_height_to_fit_content: true,
|
||||||
|
handle_offset: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +55,35 @@ impl Resize {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn min_size(mut self, min_size: Vec2) -> Self {
|
||||||
|
self.min_size = min_size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_size(mut self, max_size: Vec2) -> Self {
|
||||||
|
self.max_size = max_size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Can you resize it with the mouse?
|
||||||
|
/// Note that a window can still auto-resize
|
||||||
|
pub fn resizable(mut self, resizable: bool) -> Self {
|
||||||
|
self.resizable = resizable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fixed_size(mut self, size: Vec2) -> Self {
|
||||||
|
self.auto_shrink_width = false;
|
||||||
|
self.auto_shrink_height = false;
|
||||||
|
self.expand_width_to_fit_content = false;
|
||||||
|
self.expand_height_to_fit_content = false;
|
||||||
|
self.default_size = size;
|
||||||
|
self.min_size = size;
|
||||||
|
self.max_size = size;
|
||||||
|
self.resizable = false;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_wide_as_possible(mut self) -> Self {
|
pub fn as_wide_as_possible(mut self) -> Self {
|
||||||
self.min_size.x = f32::INFINITY;
|
self.min_size.x = f32::INFINITY;
|
||||||
self
|
self
|
||||||
|
@ -60,11 +96,31 @@ impl Resize {
|
||||||
self.expand_height_to_fit_content = auto_expand;
|
self.expand_height_to_fit_content = auto_expand;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Offset the position of the resize handle by this much
|
||||||
|
pub fn handle_offset(mut self, handle_offset: Vec2) -> Self {
|
||||||
|
self.handle_offset = handle_offset;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn auto_shrink_width(mut self, auto_shrink_width: bool) -> Self {
|
||||||
|
self.auto_shrink_width = auto_shrink_width;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn auto_shrink_height(mut self, auto_shrink_height: bool) -> Self {
|
||||||
|
self.auto_shrink_height = auto_shrink_height;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: a common trait for Things that follow this pattern
|
// TODO: a common trait for Things that follow this pattern
|
||||||
impl Resize {
|
impl Resize {
|
||||||
pub fn show(mut self, region: &mut Region, add_contents: impl FnOnce(&mut Region)) {
|
pub fn show(mut self, region: &mut Region, add_contents: impl FnOnce(&mut Region)) {
|
||||||
|
if !self.resizable {
|
||||||
|
return add_contents(region);
|
||||||
|
}
|
||||||
|
|
||||||
let id = region.make_child_id("scroll");
|
let id = region.make_child_id("scroll");
|
||||||
self.min_size = self.min_size.min(region.available_space());
|
self.min_size = self.min_size.min(region.available_space());
|
||||||
self.max_size = self.max_size.min(region.available_space());
|
self.max_size = self.max_size.min(region.available_space());
|
||||||
|
@ -84,7 +140,10 @@ impl Resize {
|
||||||
|
|
||||||
// Resize-corner:
|
// Resize-corner:
|
||||||
let corner_size = Vec2::splat(16.0); // TODO: style
|
let corner_size = Vec2::splat(16.0); // TODO: style
|
||||||
let corner_rect = Rect::from_min_size(position + state.size - corner_size, corner_size);
|
let corner_rect = Rect::from_min_size(
|
||||||
|
position + state.size + self.handle_offset - corner_size,
|
||||||
|
corner_size,
|
||||||
|
);
|
||||||
let corner_interact = region.interact_rect(&corner_rect, id.with("corner"));
|
let corner_interact = region.interact_rect(&corner_rect, id.with("corner"));
|
||||||
|
|
||||||
if corner_interact.active {
|
if corner_interact.active {
|
||||||
|
@ -109,10 +168,10 @@ impl Resize {
|
||||||
|
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
|
|
||||||
if self.shrink_width_to_fit_content {
|
if self.auto_shrink_width {
|
||||||
state.size.x = state.size.x.min(desired_size.x);
|
state.size.x = state.size.x.min(desired_size.x);
|
||||||
}
|
}
|
||||||
if self.shrink_height_to_fit_content {
|
if self.auto_shrink_height {
|
||||||
state.size.y = state.size.y.min(desired_size.y);
|
state.size.y = state.size.y.min(desired_size.y);
|
||||||
}
|
}
|
||||||
if self.expand_width_to_fit_content || is_new {
|
if self.expand_width_to_fit_content || is_new {
|
80
emigui/src/containers/window.rs
Normal file
80
emigui/src/containers/window.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{widgets::*, *};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// TODO: separate out resizing into a contained and reusable Resize-region.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Window {
|
||||||
|
title: String,
|
||||||
|
floating: Floating,
|
||||||
|
frame: Frame,
|
||||||
|
resize: Resize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn new(title: impl Into<String>) -> Self {
|
||||||
|
let title = title.into();
|
||||||
|
Self {
|
||||||
|
title: title.clone(),
|
||||||
|
floating: Floating::new(title),
|
||||||
|
frame: Frame::default(),
|
||||||
|
resize: Resize::default()
|
||||||
|
.handle_offset(Vec2::splat(4.0))
|
||||||
|
.auto_shrink_height(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_pos(mut self, default_pos: Pos2) -> Self {
|
||||||
|
self.floating = self.floating.default_pos(default_pos);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_size(mut self, default_size: Vec2) -> Self {
|
||||||
|
self.resize = self.resize.default_size(default_size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min_size(mut self, min_size: Vec2) -> Self {
|
||||||
|
self.resize = self.resize.min_size(min_size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_size(mut self, max_size: Vec2) -> Self {
|
||||||
|
self.resize = self.resize.max_size(max_size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fixed_size(mut self, size: Vec2) -> Self {
|
||||||
|
self.resize = self.resize.fixed_size(size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Can you resize it with the mouse?
|
||||||
|
/// Note that a window can still auto-resize
|
||||||
|
pub fn resizable(mut self, resizable: bool) -> Self {
|
||||||
|
self.resize = self.resize.resizable(resizable);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn show(self, ctx: &Arc<Context>, add_contents: impl FnOnce(&mut Region)) {
|
||||||
|
let Window {
|
||||||
|
title,
|
||||||
|
floating,
|
||||||
|
frame,
|
||||||
|
resize,
|
||||||
|
} = self;
|
||||||
|
floating.show(ctx, |region| {
|
||||||
|
frame.show(region, |region| {
|
||||||
|
resize.show(region, |region| {
|
||||||
|
region.add(Label::new(title).text_style(TextStyle::Heading));
|
||||||
|
region.add(Separator::new().line_width(1.0)); // TODO: nicer way to split window title from contents
|
||||||
|
add_contents(region);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,7 +76,7 @@ impl Context {
|
||||||
|
|
||||||
pub fn drain_paint_lists(&self) -> Vec<(Rect, PaintCmd)> {
|
pub fn drain_paint_lists(&self) -> Vec<(Rect, PaintCmd)> {
|
||||||
let memory = self.memory.lock();
|
let memory = self.memory.lock();
|
||||||
self.graphics.lock().drain(&memory.window_order).collect()
|
self.graphics.lock().drain(&memory.floating_order).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is the user interacting with anything?
|
/// Is the user interacting with anything?
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{color::*, widgets::*, *};
|
use crate::{color::*, containers::*, widgets::*, *};
|
||||||
|
|
||||||
/// Showcase some region code
|
/// Showcase some region code
|
||||||
pub struct ExampleApp {
|
pub struct ExampleApp {
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
//! 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,15 +6,13 @@ extern crate serde;
|
||||||
#[macro_use] // TODO: get rid of this
|
#[macro_use] // TODO: get rid of this
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
mod collapsing_header;
|
|
||||||
pub mod color;
|
pub mod color;
|
||||||
|
pub mod containers;
|
||||||
mod context;
|
mod context;
|
||||||
mod emigui;
|
mod emigui;
|
||||||
pub mod example_app;
|
pub mod example_app;
|
||||||
mod floating;
|
|
||||||
mod font;
|
mod font;
|
||||||
mod fonts;
|
mod fonts;
|
||||||
mod frame;
|
|
||||||
mod id;
|
mod id;
|
||||||
mod layers;
|
mod layers;
|
||||||
mod layout;
|
mod layout;
|
||||||
|
@ -22,22 +20,16 @@ pub mod math;
|
||||||
mod memory;
|
mod memory;
|
||||||
pub mod mesher;
|
pub mod mesher;
|
||||||
mod region;
|
mod region;
|
||||||
mod resize;
|
|
||||||
mod scroll_area;
|
|
||||||
mod style;
|
mod style;
|
||||||
mod texture_atlas;
|
mod texture_atlas;
|
||||||
mod types;
|
mod types;
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
mod window;
|
|
||||||
|
|
||||||
pub use {
|
pub use {
|
||||||
crate::emigui::Emigui,
|
crate::emigui::Emigui,
|
||||||
collapsing_header::CollapsingHeader,
|
|
||||||
color::Color,
|
color::Color,
|
||||||
context::Context,
|
context::Context,
|
||||||
floating::Floating,
|
|
||||||
fonts::{FontDefinitions, Fonts, TextStyle},
|
fonts::{FontDefinitions, Fonts, TextStyle},
|
||||||
frame::Frame,
|
|
||||||
id::Id,
|
id::Id,
|
||||||
layers::*,
|
layers::*,
|
||||||
layout::{Align, GuiResponse},
|
layout::{Align, GuiResponse},
|
||||||
|
@ -45,10 +37,7 @@ pub use {
|
||||||
memory::Memory,
|
memory::Memory,
|
||||||
mesher::{Mesh, PaintBatches, Vertex},
|
mesher::{Mesh, PaintBatches, Vertex},
|
||||||
region::Region,
|
region::Region,
|
||||||
resize::Resize,
|
|
||||||
scroll_area::ScrollArea,
|
|
||||||
style::Style,
|
style::Style,
|
||||||
texture_atlas::Texture,
|
texture_atlas::Texture,
|
||||||
types::*,
|
types::*,
|
||||||
window::Window,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{collapsing_header, resize, scroll_area, window, *};
|
use crate::{
|
||||||
|
containers::{collapsing_header, floating, resize, scroll_area},
|
||||||
|
Id, Layer, Pos2, Rect,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Memory {
|
pub struct Memory {
|
||||||
|
@ -11,43 +14,44 @@ pub struct Memory {
|
||||||
pub(crate) collapsing_headers: HashMap<Id, collapsing_header::State>,
|
pub(crate) collapsing_headers: HashMap<Id, collapsing_header::State>,
|
||||||
pub(crate) scroll_areas: HashMap<Id, scroll_area::State>,
|
pub(crate) scroll_areas: HashMap<Id, scroll_area::State>,
|
||||||
pub(crate) resize: HashMap<Id, resize::State>,
|
pub(crate) resize: HashMap<Id, resize::State>,
|
||||||
windows: HashMap<Id, window::State>,
|
floating: HashMap<Id, floating::State>,
|
||||||
|
|
||||||
/// Top is last
|
/// Top is last
|
||||||
pub window_order: Vec<Id>,
|
pub floating_order: Vec<Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Memory {
|
impl Memory {
|
||||||
pub fn get_window(&mut self, id: Id) -> Option<window::State> {
|
pub fn get_floating(&mut self, id: Id) -> Option<floating::State> {
|
||||||
self.windows.get(&id).cloned()
|
self.floating.get(&id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_window_state(&mut self, id: Id, state: window::State) {
|
pub fn set_floating_state(&mut self, id: Id, state: floating::State) {
|
||||||
let did_insert = self.windows.insert(id, state).is_none();
|
let did_insert = self.floating.insert(id, state).is_none();
|
||||||
if did_insert {
|
if did_insert {
|
||||||
self.window_order.push(id);
|
self.floating_order.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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) -> Layer {
|
||||||
for window_id in self.window_order.iter().rev() {
|
for floating_id in self.floating_order.iter().rev() {
|
||||||
if let Some(state) = self.windows.get(window_id) {
|
if let Some(state) = self.floating.get(floating_id) {
|
||||||
if state.outer_rect.contains(pos) {
|
let rect = Rect::from_min_size(state.pos, state.size);
|
||||||
return Layer::Window(*window_id);
|
if rect.contains(pos) {
|
||||||
|
return Layer::Window(*floating_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Layer::Background
|
Layer::Background
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_window_to_top(&mut self, id: Id) {
|
pub fn move_floating_to_top(&mut self, id: Id) {
|
||||||
if self.window_order.last() == Some(&id) {
|
if self.floating_order.last() == Some(&id) {
|
||||||
return; // common case early-out
|
return; // common case early-out
|
||||||
}
|
}
|
||||||
if let Some(index) = self.window_order.iter().position(|x| *x == id) {
|
if let Some(index) = self.floating_order.iter().position(|x| *x == id) {
|
||||||
self.window_order.remove(index);
|
self.floating_order.remove(index);
|
||||||
}
|
}
|
||||||
self.window_order.push(id);
|
self.floating_order.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{hash::Hash, sync::Arc};
|
use std::{hash::Hash, sync::Arc};
|
||||||
|
|
||||||
use crate::{color::*, font::TextFragment, layout::*, widgets::*, *};
|
use crate::{color::*, containers::*, font::TextFragment, layout::*, widgets::*, *};
|
||||||
|
|
||||||
/// Represents a region of the screen
|
/// Represents a region of the screen
|
||||||
/// with a type of layout (horizontal or vertical).
|
/// with a type of layout (horizontal or vertical).
|
||||||
|
|
|
@ -1,300 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::{mesher::Path, widgets::*, *};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct State {
|
|
||||||
/// Last known pos
|
|
||||||
pub outer_pos: Pos2,
|
|
||||||
pub inner_size: Vec2,
|
|
||||||
|
|
||||||
/// used for catching clicks:
|
|
||||||
pub outer_rect: Rect,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: separate out resizing into a contained and reusable Resize-region.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Window {
|
|
||||||
/// The title of the window and by default the source of its identity.
|
|
||||||
title: String,
|
|
||||||
/// Put the window here the first time
|
|
||||||
default_pos: Option<Pos2>,
|
|
||||||
|
|
||||||
/// Size of the window first time
|
|
||||||
default_size: Option<Vec2>,
|
|
||||||
|
|
||||||
resizeable: bool,
|
|
||||||
|
|
||||||
// If true, won't allow you to make window so big that it creates spacing
|
|
||||||
shrink_width_to_fit_content: bool,
|
|
||||||
shrink_height_to_fit_content: bool,
|
|
||||||
|
|
||||||
// If true, won't allow you to resize smaller than that everything fits.
|
|
||||||
expand_width_to_fit_content: bool,
|
|
||||||
expand_height_to_fit_content: bool,
|
|
||||||
|
|
||||||
min_size: Vec2,
|
|
||||||
max_size: Option<Vec2>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Window {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
title: "".to_owned(),
|
|
||||||
default_pos: None,
|
|
||||||
default_size: None,
|
|
||||||
resizeable: true,
|
|
||||||
shrink_width_to_fit_content: false,
|
|
||||||
shrink_height_to_fit_content: false,
|
|
||||||
expand_width_to_fit_content: true,
|
|
||||||
expand_height_to_fit_content: true,
|
|
||||||
min_size: Vec2::splat(16.0),
|
|
||||||
max_size: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub fn new(title: impl Into<String>) -> Self {
|
|
||||||
Self {
|
|
||||||
title: title.into(),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn default_pos(mut self, default_pos: Pos2) -> Self {
|
|
||||||
self.default_pos = Some(default_pos);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn default_size(mut self, default_size: Vec2) -> Self {
|
|
||||||
self.default_size = Some(default_size);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn min_size(mut self, min_size: Vec2) -> Self {
|
|
||||||
self.min_size = min_size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn max_size(mut self, max_size: Vec2) -> Self {
|
|
||||||
self.max_size = Some(max_size);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fixed_size(mut self, size: Vec2) -> Self {
|
|
||||||
self.shrink_width_to_fit_content = false;
|
|
||||||
self.shrink_height_to_fit_content = false;
|
|
||||||
self.expand_width_to_fit_content = false;
|
|
||||||
self.expand_height_to_fit_content = false;
|
|
||||||
self.default_size = Some(size);
|
|
||||||
self.min_size = size;
|
|
||||||
self.max_size = Some(size);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Can you resize it with the mouse?
|
|
||||||
/// Note that a window can still auto-resize
|
|
||||||
pub fn resizeable(mut self, resizeable: bool) -> Self {
|
|
||||||
self.resizeable = resizeable;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn shrink_to_fit_content(mut self, shrink_to_fit_content: bool) -> Self {
|
|
||||||
// self.shrink_to_fit_content = shrink_to_fit_content;
|
|
||||||
// self
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn expand_to_fit_content(mut self, expand_to_fit_content: bool) -> Self {
|
|
||||||
// self.expand_to_fit_content = expand_to_fit_content;
|
|
||||||
// self
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub fn show(self, ctx: &Arc<Context>, add_contents: impl FnOnce(&mut Region)) {
|
|
||||||
let style = ctx.style();
|
|
||||||
let window_padding = style.window_padding;
|
|
||||||
|
|
||||||
let default_pos = self.default_pos.unwrap_or_else(|| pos2(100.0, 100.0)); // TODO
|
|
||||||
let default_inner_size = self.default_size.unwrap_or_else(|| vec2(250.0, 250.0));
|
|
||||||
|
|
||||||
let id = ctx.make_unique_id(&self.title, default_pos);
|
|
||||||
|
|
||||||
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: default_inner_size,
|
|
||||||
outer_rect: Rect::from_min_size(
|
|
||||||
default_pos,
|
|
||||||
default_inner_size + 2.0 * window_padding,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
(state, true)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
state.outer_pos = state.outer_pos.round(); // TODO: round to pixel
|
|
||||||
|
|
||||||
let min_inner_size = self.min_size;
|
|
||||||
let max_inner_size = self
|
|
||||||
.max_size
|
|
||||||
.unwrap_or(Pos2::default() + ctx.input.screen_size - state.outer_pos);
|
|
||||||
|
|
||||||
let layer = Layer::Window(id);
|
|
||||||
let where_to_put_background = ctx.graphics.lock().layer(layer).len();
|
|
||||||
|
|
||||||
let inner_rect = Rect::from_min_size(state.outer_pos + window_padding, state.inner_size);
|
|
||||||
let mut contents_region = Region::new(ctx.clone(), layer, id, inner_rect);
|
|
||||||
// TODO: handle contents_region.clip_rect while resizing
|
|
||||||
|
|
||||||
// Show top bar:
|
|
||||||
contents_region.add(Label::new(self.title).text_style(TextStyle::Heading));
|
|
||||||
contents_region.add(Separator::new().line_width(1.0).extra(window_padding.x)); // TODO: nicer way to split window title from contents
|
|
||||||
|
|
||||||
add_contents(&mut contents_region);
|
|
||||||
|
|
||||||
// TODO: handle the last item_spacing in a nicer way
|
|
||||||
let desired_inner_size = contents_region.bounding_size() - style.item_spacing;
|
|
||||||
let desired_inner_size = desired_inner_size.ceil(); // Avoid rounding errors in math
|
|
||||||
|
|
||||||
let mut new_inner_size = state.inner_size;
|
|
||||||
if self.shrink_width_to_fit_content {
|
|
||||||
new_inner_size.x = new_inner_size.x.min(desired_inner_size.x);
|
|
||||||
}
|
|
||||||
if self.shrink_height_to_fit_content {
|
|
||||||
new_inner_size.y = new_inner_size.y.min(desired_inner_size.y);
|
|
||||||
}
|
|
||||||
if self.expand_width_to_fit_content || is_new {
|
|
||||||
new_inner_size.x = new_inner_size.x.max(desired_inner_size.x);
|
|
||||||
}
|
|
||||||
if self.expand_height_to_fit_content || is_new {
|
|
||||||
new_inner_size.y = new_inner_size.y.max(desired_inner_size.y);
|
|
||||||
}
|
|
||||||
new_inner_size = new_inner_size.max(min_inner_size);
|
|
||||||
new_inner_size = new_inner_size.min(max_inner_size);
|
|
||||||
|
|
||||||
let new_outer_size = new_inner_size + 2.0 * window_padding;
|
|
||||||
|
|
||||||
let outer_rect = Rect::from_min_size(state.outer_pos, new_outer_size);
|
|
||||||
|
|
||||||
let mut graphics = ctx.graphics.lock();
|
|
||||||
|
|
||||||
let corner_radius = style.window.corner_radius;
|
|
||||||
graphics.layer(layer).insert(
|
|
||||||
where_to_put_background,
|
|
||||||
(
|
|
||||||
Rect::everything(),
|
|
||||||
PaintCmd::Rect {
|
|
||||||
corner_radius,
|
|
||||||
fill_color: Some(style.background_fill_color()),
|
|
||||||
outline: Some(Outline::new(1.0, color::WHITE)),
|
|
||||||
rect: outer_rect,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let corner_interact = if self.resizeable {
|
|
||||||
// Resize-corner:
|
|
||||||
let corner_center = outer_rect.max - Vec2::splat(corner_radius);
|
|
||||||
let corner_rect = Rect::from_min_size(corner_center, Vec2::splat(corner_radius));
|
|
||||||
|
|
||||||
let corner_interact = ctx.interact(layer, &corner_rect, Some(id.with("corner")));
|
|
||||||
|
|
||||||
graphics.layer(layer).push((
|
|
||||||
Rect::everything(),
|
|
||||||
paint_resize_corner(corner_center, corner_radius, &style, &corner_interact),
|
|
||||||
));
|
|
||||||
corner_interact
|
|
||||||
} else {
|
|
||||||
InteractInfo::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let win_interact = ctx.interact(layer, &outer_rect, Some(id.with("window")));
|
|
||||||
|
|
||||||
if corner_interact.active {
|
|
||||||
if let Some(mouse_pos) = ctx.input().mouse_pos {
|
|
||||||
let new_outer_size =
|
|
||||||
mouse_pos - state.outer_pos + 0.5 * corner_interact.rect.size();
|
|
||||||
new_inner_size = new_outer_size - 2.0 * window_padding;
|
|
||||||
new_inner_size = new_inner_size.max(min_inner_size);
|
|
||||||
new_inner_size = new_inner_size.min(max_inner_size);
|
|
||||||
}
|
|
||||||
} else if win_interact.active {
|
|
||||||
state.outer_pos += ctx.input().mouse_move;
|
|
||||||
}
|
|
||||||
|
|
||||||
if corner_interact.hovered || corner_interact.active {
|
|
||||||
ctx.output.lock().cursor_icon = CursorIcon::ResizeNwSe;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = State {
|
|
||||||
outer_pos: state.outer_pos,
|
|
||||||
inner_size: new_inner_size,
|
|
||||||
outer_rect,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Constrain to screen:
|
|
||||||
let margin = 32.0;
|
|
||||||
state.outer_pos = state.outer_pos.max(pos2(margin - outer_rect.width(), 0.0));
|
|
||||||
state.outer_pos = state.outer_pos.min(pos2(
|
|
||||||
ctx.input.screen_size.x - margin,
|
|
||||||
ctx.input.screen_size.y - margin,
|
|
||||||
));
|
|
||||||
|
|
||||||
if win_interact.active || corner_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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint_resize_corner(
|
|
||||||
center: Pos2,
|
|
||||||
radius: f32,
|
|
||||||
style: &Style,
|
|
||||||
interact: &InteractInfo,
|
|
||||||
) -> PaintCmd {
|
|
||||||
if false {
|
|
||||||
// TODO: Path::circle_sector() or something
|
|
||||||
let quadrant = 0.0; // Bottom-right
|
|
||||||
let mut path = Path::default();
|
|
||||||
path.add_point(center, vec2(0.0, -1.0));
|
|
||||||
path.add_point(center + vec2(radius, 0.0), vec2(0.0, -1.0));
|
|
||||||
path.add_circle_quadrant(center, radius, quadrant);
|
|
||||||
path.add_point(center + vec2(0.0, radius), vec2(-1.0, 0.0));
|
|
||||||
path.add_point(center, vec2(-1.0, 0.0));
|
|
||||||
PaintCmd::Path {
|
|
||||||
path,
|
|
||||||
closed: true,
|
|
||||||
fill_color: style.interact_fill_color(&interact),
|
|
||||||
outline: style.interact_outline(&interact),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let offset = 3.0;
|
|
||||||
let center = center;
|
|
||||||
let radius = radius - offset;
|
|
||||||
let quadrant = 0.0; // Bottom-right
|
|
||||||
let mut path = Path::default();
|
|
||||||
path.add_circle_quadrant(center, radius, quadrant);
|
|
||||||
PaintCmd::Path {
|
|
||||||
path,
|
|
||||||
closed: false,
|
|
||||||
fill_color: None,
|
|
||||||
outline: Some(Outline::new(
|
|
||||||
style.interact_stroke_width(&interact),
|
|
||||||
style.interact_stroke_color(&interact),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use {
|
use {
|
||||||
emigui::{example_app::ExampleApp, widgets::*, *},
|
emigui::{containers::*, example_app::ExampleApp, widgets::*, *},
|
||||||
emigui_glium::Painter,
|
emigui_glium::Painter,
|
||||||
glium::glutin,
|
glium::glutin,
|
||||||
};
|
};
|
||||||
|
@ -121,19 +121,6 @@ fn main() {
|
||||||
emigui.ui(region);
|
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();
|
let (output, paint_batches) = emigui.end_frame();
|
||||||
painter.paint_batches(&display, paint_batches, emigui.texture());
|
painter.paint_batches(&display, paint_batches, emigui.texture());
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,11 @@ extern crate emigui_wasm;
|
||||||
use {
|
use {
|
||||||
emigui::{
|
emigui::{
|
||||||
color::srgba,
|
color::srgba,
|
||||||
|
containers::*,
|
||||||
example_app::ExampleApp,
|
example_app::ExampleApp,
|
||||||
label,
|
label,
|
||||||
widgets::{Label, Separator},
|
widgets::{Label, Separator},
|
||||||
Align, Emigui, RawInput, TextStyle, Window, *,
|
Align, Emigui, RawInput, TextStyle, *,
|
||||||
},
|
},
|
||||||
emigui_wasm::now_sec,
|
emigui_wasm::now_sec,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue