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
|
||||
//!
|
||||
|
||||
use crate::*;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Frame {}
|
||||
|
||||
impl Frame {
|
|
@ -8,6 +8,9 @@ pub struct State {
|
|||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Resize {
|
||||
/// If false, we are no enabled
|
||||
resizable: bool,
|
||||
|
||||
// Will still try to stay within parent region bounds
|
||||
min_size: Vec2,
|
||||
max_size: Vec2,
|
||||
|
@ -15,24 +18,28 @@ pub struct Resize {
|
|||
default_size: Vec2,
|
||||
|
||||
// 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,
|
||||
auto_shrink_width: bool,
|
||||
auto_shrink_height: 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,
|
||||
|
||||
handle_offset: Vec2,
|
||||
}
|
||||
|
||||
impl Default for Resize {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
resizable: true,
|
||||
min_size: Vec2::splat(32.0),
|
||||
max_size: Vec2::infinity(),
|
||||
default_size: vec2(f32::INFINITY, 200.0), // TODO
|
||||
shrink_width_to_fit_content: false,
|
||||
shrink_height_to_fit_content: false,
|
||||
auto_shrink_width: false,
|
||||
auto_shrink_height: false,
|
||||
expand_width_to_fit_content: true,
|
||||
expand_height_to_fit_content: true,
|
||||
handle_offset: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +55,35 @@ impl Resize {
|
|||
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 {
|
||||
self.min_size.x = f32::INFINITY;
|
||||
self
|
||||
|
@ -60,11 +96,31 @@ impl Resize {
|
|||
self.expand_height_to_fit_content = auto_expand;
|
||||
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
|
||||
impl Resize {
|
||||
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");
|
||||
self.min_size = self.min_size.min(region.available_space());
|
||||
self.max_size = self.max_size.min(region.available_space());
|
||||
|
@ -84,7 +140,10 @@ impl Resize {
|
|||
|
||||
// Resize-corner:
|
||||
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"));
|
||||
|
||||
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);
|
||||
}
|
||||
if self.shrink_height_to_fit_content {
|
||||
if self.auto_shrink_height {
|
||||
state.size.y = state.size.y.min(desired_size.y);
|
||||
}
|
||||
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)> {
|
||||
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?
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{color::*, widgets::*, *};
|
||||
use crate::{color::*, containers::*, widgets::*, *};
|
||||
|
||||
/// Showcase some region code
|
||||
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
|
||||
extern crate serde_derive;
|
||||
|
||||
mod collapsing_header;
|
||||
pub mod color;
|
||||
pub mod containers;
|
||||
mod context;
|
||||
mod emigui;
|
||||
pub mod example_app;
|
||||
mod floating;
|
||||
mod font;
|
||||
mod fonts;
|
||||
mod frame;
|
||||
mod id;
|
||||
mod layers;
|
||||
mod layout;
|
||||
|
@ -22,22 +20,16 @@ pub mod math;
|
|||
mod memory;
|
||||
pub mod mesher;
|
||||
mod region;
|
||||
mod resize;
|
||||
mod scroll_area;
|
||||
mod style;
|
||||
mod texture_atlas;
|
||||
mod types;
|
||||
pub mod widgets;
|
||||
mod window;
|
||||
|
||||
pub use {
|
||||
crate::emigui::Emigui,
|
||||
collapsing_header::CollapsingHeader,
|
||||
color::Color,
|
||||
context::Context,
|
||||
floating::Floating,
|
||||
fonts::{FontDefinitions, Fonts, TextStyle},
|
||||
frame::Frame,
|
||||
id::Id,
|
||||
layers::*,
|
||||
layout::{Align, GuiResponse},
|
||||
|
@ -45,10 +37,7 @@ pub use {
|
|||
memory::Memory,
|
||||
mesher::{Mesh, PaintBatches, Vertex},
|
||||
region::Region,
|
||||
resize::Resize,
|
||||
scroll_area::ScrollArea,
|
||||
style::Style,
|
||||
texture_atlas::Texture,
|
||||
types::*,
|
||||
window::Window,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
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)]
|
||||
pub struct Memory {
|
||||
|
@ -11,43 +14,44 @@ pub struct Memory {
|
|||
pub(crate) collapsing_headers: HashMap<Id, collapsing_header::State>,
|
||||
pub(crate) scroll_areas: HashMap<Id, scroll_area::State>,
|
||||
pub(crate) resize: HashMap<Id, resize::State>,
|
||||
windows: HashMap<Id, window::State>,
|
||||
floating: HashMap<Id, floating::State>,
|
||||
|
||||
/// Top is last
|
||||
pub window_order: Vec<Id>,
|
||||
pub floating_order: Vec<Id>,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
pub fn get_window(&mut self, id: Id) -> Option<window::State> {
|
||||
self.windows.get(&id).cloned()
|
||||
pub fn get_floating(&mut self, id: Id) -> Option<floating::State> {
|
||||
self.floating.get(&id).cloned()
|
||||
}
|
||||
|
||||
pub fn set_window_state(&mut self, id: Id, state: window::State) {
|
||||
let did_insert = self.windows.insert(id, state).is_none();
|
||||
pub fn set_floating_state(&mut self, id: Id, state: floating::State) {
|
||||
let did_insert = self.floating.insert(id, state).is_none();
|
||||
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
|
||||
pub fn layer_at(&self, pos: Pos2) -> Layer {
|
||||
for window_id in self.window_order.iter().rev() {
|
||||
if let Some(state) = self.windows.get(window_id) {
|
||||
if state.outer_rect.contains(pos) {
|
||||
return Layer::Window(*window_id);
|
||||
for floating_id in self.floating_order.iter().rev() {
|
||||
if let Some(state) = self.floating.get(floating_id) {
|
||||
let rect = Rect::from_min_size(state.pos, state.size);
|
||||
if rect.contains(pos) {
|
||||
return Layer::Window(*floating_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Layer::Background
|
||||
}
|
||||
|
||||
pub fn move_window_to_top(&mut self, id: Id) {
|
||||
if self.window_order.last() == Some(&id) {
|
||||
pub fn move_floating_to_top(&mut self, id: Id) {
|
||||
if self.floating_order.last() == Some(&id) {
|
||||
return; // common case early-out
|
||||
}
|
||||
if let Some(index) = self.window_order.iter().position(|x| *x == id) {
|
||||
self.window_order.remove(index);
|
||||
if let Some(index) = self.floating_order.iter().position(|x| *x == id) {
|
||||
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 crate::{color::*, font::TextFragment, layout::*, widgets::*, *};
|
||||
use crate::{color::*, containers::*, font::TextFragment, layout::*, widgets::*, *};
|
||||
|
||||
/// Represents a region of the screen
|
||||
/// 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 {
|
||||
emigui::{example_app::ExampleApp, widgets::*, *},
|
||||
emigui::{containers::*, example_app::ExampleApp, widgets::*, *},
|
||||
emigui_glium::Painter,
|
||||
glium::glutin,
|
||||
};
|
||||
|
@ -121,19 +121,6 @@ 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());
|
||||
|
||||
|
|
|
@ -9,10 +9,11 @@ extern crate emigui_wasm;
|
|||
use {
|
||||
emigui::{
|
||||
color::srgba,
|
||||
containers::*,
|
||||
example_app::ExampleApp,
|
||||
label,
|
||||
widgets::{Label, Separator},
|
||||
Align, Emigui, RawInput, TextStyle, Window, *,
|
||||
Align, Emigui, RawInput, TextStyle, *,
|
||||
},
|
||||
emigui_wasm::now_sec,
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue