1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 13:20:05 -04:00

Add Floating and Frame containers as building blocks for windows

This commit is contained in:
Emil Ernerfeldt
2020-04-25 15:26:24 +02:00
parent 3a430c8fc7
commit e26d08851f
8 changed files with 212 additions and 17 deletions

View File

@@ -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
View 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
View 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
}
}

View File

@@ -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},

View File

@@ -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)
}

View File

@@ -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