mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 13:20:05 -04:00
Rename foldable to CollapsingHeader and move to own file
This commit is contained in:
156
emigui/src/collapsing_header.rs
Normal file
156
emigui/src/collapsing_header.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use crate::{layout::Direction, *};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct State {
|
||||
pub open: bool,
|
||||
pub toggle_time: f64,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
open: false,
|
||||
toggle_time: -std::f64::INFINITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CollapsingHeader {
|
||||
title: String,
|
||||
default_open: bool,
|
||||
}
|
||||
|
||||
impl CollapsingHeader {
|
||||
pub fn new(title: impl Into<String>) -> Self {
|
||||
Self {
|
||||
title: title.into(),
|
||||
default_open: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_open(mut self) -> Self {
|
||||
self.default_open = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl CollapsingHeader {
|
||||
pub fn show(self, region: &mut Region, add_contents: impl FnOnce(&mut Region)) -> GuiResponse {
|
||||
assert!(
|
||||
region.dir == Direction::Vertical,
|
||||
"Horizontal collapsing is unimplemented"
|
||||
);
|
||||
let Self {
|
||||
title,
|
||||
default_open,
|
||||
} = self;
|
||||
|
||||
let id = region.make_unique_id(&title);
|
||||
let text_style = TextStyle::Button;
|
||||
let font = ®ion.fonts()[text_style];
|
||||
let (title, text_size) = font.layout_multiline(&title, region.available_width());
|
||||
let text_cursor = region.cursor + region.style.button_padding;
|
||||
let interact = region.reserve_space(
|
||||
vec2(
|
||||
region.available_width(),
|
||||
text_size.y + 2.0 * region.style.button_padding.y,
|
||||
),
|
||||
Some(id),
|
||||
);
|
||||
|
||||
let state = {
|
||||
let mut memory = region.ctx.memory.lock();
|
||||
let mut state = memory.collapsing_headers.entry(id).or_insert(State {
|
||||
open: default_open,
|
||||
..Default::default()
|
||||
});
|
||||
if interact.clicked {
|
||||
state.open = !state.open;
|
||||
state.toggle_time = region.ctx.input.time;
|
||||
}
|
||||
*state
|
||||
};
|
||||
|
||||
region.add_paint_cmd(PaintCmd::Rect {
|
||||
corner_radius: region.style.interact_corner_radius(&interact),
|
||||
fill_color: region.style.interact_fill_color(&interact),
|
||||
outline: region.style().interact_outline(&interact),
|
||||
rect: interact.rect,
|
||||
});
|
||||
|
||||
paint_icon(region, &state, &interact);
|
||||
|
||||
region.add_text(
|
||||
text_cursor + vec2(region.style.start_icon_width, 0.0),
|
||||
text_style,
|
||||
title,
|
||||
Some(region.style.interact_stroke_color(&interact)),
|
||||
);
|
||||
|
||||
let animation_time = region.style().animation_time;
|
||||
let time_since_toggle = (region.ctx.input.time - state.toggle_time) as f32;
|
||||
if time_since_toggle < animation_time {
|
||||
region.indent(id, |region| {
|
||||
// animation time
|
||||
|
||||
let max_height = if state.open {
|
||||
remap(
|
||||
time_since_toggle,
|
||||
0.0,
|
||||
animation_time,
|
||||
50.0, // Get instant feedback
|
||||
1500.0, // We don't expect to get bigger than this
|
||||
)
|
||||
} else {
|
||||
remap_clamp(
|
||||
time_since_toggle,
|
||||
0.0,
|
||||
animation_time,
|
||||
50.0, // TODO: state.open_height
|
||||
0.0,
|
||||
)
|
||||
};
|
||||
|
||||
region
|
||||
.clip_rect
|
||||
.set_height(region.clip_rect.height().min(max_height));
|
||||
|
||||
add_contents(region);
|
||||
|
||||
region.bounding_size.y = region.bounding_size.y.min(max_height);
|
||||
});
|
||||
} else if state.open {
|
||||
region.indent(id, add_contents);
|
||||
}
|
||||
|
||||
region.response(interact)
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_icon(region: &mut Region, state: &State, interact: &InteractInfo) {
|
||||
let stroke_color = region.style.interact_stroke_color(&interact);
|
||||
let stroke_width = region.style.interact_stroke_width(&interact);
|
||||
|
||||
let (small_icon_rect, _) = region.style.icon_rectangles(&interact.rect);
|
||||
// Draw a minus:
|
||||
region.add_paint_cmd(PaintCmd::Line {
|
||||
points: vec![
|
||||
pos2(small_icon_rect.min().x, small_icon_rect.center().y),
|
||||
pos2(small_icon_rect.max().x, small_icon_rect.center().y),
|
||||
],
|
||||
color: stroke_color,
|
||||
width: stroke_width,
|
||||
});
|
||||
|
||||
if !state.open {
|
||||
// Draw it as a plus:
|
||||
region.add_paint_cmd(PaintCmd::Line {
|
||||
points: vec![
|
||||
pos2(small_icon_rect.center().x, small_icon_rect.min().y),
|
||||
pos2(small_icon_rect.center().x, small_icon_rect.max().y),
|
||||
],
|
||||
color: stroke_color,
|
||||
width: stroke_width,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ impl Emigui {
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, region: &mut Region) {
|
||||
region.foldable("Style", |region| {
|
||||
region.collapsing("Style", |region| {
|
||||
region.add(Checkbox::new(
|
||||
&mut self.mesher_options.anti_alias,
|
||||
"Antialias",
|
||||
@@ -89,7 +89,7 @@ impl Emigui {
|
||||
self.ctx.style_ui(region);
|
||||
});
|
||||
|
||||
region.foldable("Fonts", |region| {
|
||||
region.collapsing("Fonts", |region| {
|
||||
let old_font_definitions = self.ctx.fonts.definitions();
|
||||
let mut new_font_definitions = old_font_definitions.clone();
|
||||
font_definitions_ui(&mut new_font_definitions, region);
|
||||
@@ -103,7 +103,7 @@ impl Emigui {
|
||||
}
|
||||
});
|
||||
|
||||
region.foldable("Stats", |region| {
|
||||
region.collapsing("Stats", |region| {
|
||||
region.add(label!(
|
||||
"Screen size: {} x {} points, pixels_per_point: {}",
|
||||
region.input().screen_size.x,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::{color::*, label, math::*, widgets::*, Align, Outline, PaintCmd, Region, ScrollArea};
|
||||
use crate::{
|
||||
color::*, label, math::*, widgets::*, Align, CollapsingHeader, Outline, PaintCmd, Region,
|
||||
ScrollArea,
|
||||
};
|
||||
|
||||
/// Showcase some region code
|
||||
pub struct ExampleApp {
|
||||
@@ -36,13 +39,13 @@ impl Default for ExampleApp {
|
||||
|
||||
impl ExampleApp {
|
||||
pub fn ui(&mut self, region: &mut Region) {
|
||||
region.foldable("About Emigui", |region| {
|
||||
region.collapsing("About Emigui", |region| {
|
||||
region.add(label!(
|
||||
"Emigui is an experimental immediate mode GUI written in Rust."
|
||||
));
|
||||
});
|
||||
|
||||
region.foldable("Widgets", |region| {
|
||||
region.collapsing("Widgets", |region| {
|
||||
region.horizontal(Align::Min, |region| {
|
||||
region.add(label!("Text can have").text_color(srgba(110, 255, 110, 255)));
|
||||
region.add(label!("color").text_color(srgba(128, 140, 255, 255)));
|
||||
@@ -87,7 +90,7 @@ impl ExampleApp {
|
||||
region.add(label!("Value: {}", value));
|
||||
});
|
||||
|
||||
region.foldable("Layouts", |region| {
|
||||
region.collapsing("Layouts", |region| {
|
||||
region.add(Slider::usize(&mut self.num_columns, 1, 10).text("Columns"));
|
||||
region.columns(self.num_columns, |cols| {
|
||||
for (i, col) in cols.iter_mut().enumerate() {
|
||||
@@ -101,7 +104,7 @@ impl ExampleApp {
|
||||
});
|
||||
});
|
||||
|
||||
region.foldable("Test box rendering", |region| {
|
||||
region.collapsing("Test box rendering", |region| {
|
||||
region.add(Slider::f32(&mut self.size.x, 0.0, 500.0).text("width"));
|
||||
region.add(Slider::f32(&mut self.size.y, 0.0, 500.0).text("height"));
|
||||
region.add(Slider::f32(&mut self.corner_radius, 0.0, 50.0).text("corner_radius"));
|
||||
@@ -131,26 +134,28 @@ impl ExampleApp {
|
||||
region.add_paint_cmds(cmds);
|
||||
});
|
||||
|
||||
region.foldable("Scroll area", |region| {
|
||||
ScrollArea::default().show(region, |region| {
|
||||
region.add_label(LOREM_IPSUM);
|
||||
CollapsingHeader::new("Scroll area")
|
||||
.default_open()
|
||||
.show(region, |region| {
|
||||
ScrollArea::default().show(region, |region| {
|
||||
region.add_label(LOREM_IPSUM);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
region.foldable("Name clash example", |region| {
|
||||
region.collapsing("Name clash example", |region| {
|
||||
region.add_label("\
|
||||
Regions that store state require unique identifiers so we can track their state between frames. \
|
||||
Identifiers are normally derived from the titles of the widget.");
|
||||
|
||||
region.add_label("\
|
||||
For instance, foldable regions needs to store wether or not they are open. \
|
||||
For instance, collapsing regions needs to store wether or not they are open. \
|
||||
If you fail to give them unique names then clicking one will open both. \
|
||||
To help you debug this, an error message is printed on screen:");
|
||||
|
||||
region.foldable("Foldable", |region| {
|
||||
region.collapsing("Collapsing header", |region| {
|
||||
region.add_label("Contents of first folddable region");
|
||||
});
|
||||
region.foldable("Foldable", |region| {
|
||||
region.collapsing("Collapsing header", |region| {
|
||||
region.add_label("Contents of second folddable region");
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! moved outside the slider.
|
||||
//!
|
||||
//! For some widgets `Id`s are also used to GUIpersist some state about the
|
||||
//! widgets, such as Window position or wether not a Foldable region is open.
|
||||
//! widgets, such as Window position or wether not a collapsing header region is open.
|
||||
//!
|
||||
//! This implicated that the `Id`s must be unqiue.
|
||||
//!
|
||||
@@ -15,10 +15,10 @@
|
||||
//! For instance, a slider only needs a unique and persistent ID while you are
|
||||
//! dragging the sldier. As long as it is still while moving, that is fine.
|
||||
//!
|
||||
//! For things that need to persist state even after moving (windows, foldables)
|
||||
//! For things that need to persist state even after moving (windows, collapsing headers)
|
||||
//! the location of the widgets is obviously not good enough. For instance,
|
||||
//! a fodlable region needs to remember wether or not it is open even
|
||||
//! if the layout next frame is different and the foldable is not lower down
|
||||
//! if the layout next frame is different and the collapsing is not lower down
|
||||
//! on the screen.
|
||||
//!
|
||||
//! Then there are widgets that need no identifiers at all, like labels,
|
||||
|
||||
@@ -6,6 +6,7 @@ extern crate serde;
|
||||
#[macro_use] // TODO: get rid of this
|
||||
extern crate serde_derive;
|
||||
|
||||
mod collapsing_header;
|
||||
pub mod color;
|
||||
mod context;
|
||||
mod emigui;
|
||||
@@ -28,12 +29,13 @@ mod window;
|
||||
|
||||
pub use {
|
||||
crate::emigui::Emigui,
|
||||
collapsing_header::CollapsingHeader,
|
||||
color::Color,
|
||||
context::{Context, CursorIcon},
|
||||
fonts::{FontDefinitions, Fonts, TextStyle},
|
||||
id::Id,
|
||||
layers::*,
|
||||
layout::Align,
|
||||
layout::{Align, GuiResponse},
|
||||
math::*,
|
||||
memory::Memory,
|
||||
mesher::{Mesh, PaintBatches, Vertex},
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{window::WindowState, *};
|
||||
use crate::{collapsing_header, window::WindowState, *};
|
||||
|
||||
// TODO: move together with foldable code into own file
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FoldableState {
|
||||
pub open: bool,
|
||||
pub toggle_time: f64,
|
||||
}
|
||||
|
||||
impl Default for FoldableState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
open: false,
|
||||
toggle_time: -std::f64::INFINITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct ScrollState {
|
||||
/// Positive offset means scrolling down/right
|
||||
@@ -29,7 +14,7 @@ pub struct Memory {
|
||||
pub(crate) active_id: Option<Id>,
|
||||
|
||||
// states of various types of widgets
|
||||
pub(crate) foldables: HashMap<Id, FoldableState>,
|
||||
pub(crate) collapsing_headers: HashMap<Id, collapsing_header::State>,
|
||||
pub(crate) scroll_areas: HashMap<Id, ScrollState>,
|
||||
windows: HashMap<Id, WindowState>,
|
||||
|
||||
|
||||
@@ -143,118 +143,6 @@ impl Region {
|
||||
// ------------------------------------------------------------------------
|
||||
// Sub-regions:
|
||||
|
||||
pub fn foldable<S, F>(&mut self, text: S, add_contents: F) -> GuiResponse
|
||||
where
|
||||
S: Into<String>,
|
||||
F: FnOnce(&mut Region),
|
||||
{
|
||||
assert!(
|
||||
self.dir == Direction::Vertical,
|
||||
"Horizontal foldable is unimplemented"
|
||||
);
|
||||
let text: String = text.into();
|
||||
let id = self.make_unique_id(&text);
|
||||
let text_style = TextStyle::Button;
|
||||
let font = &self.fonts()[text_style];
|
||||
let (text, text_size) = font.layout_multiline(&text, self.available_width());
|
||||
let text_cursor = self.cursor + self.style.button_padding;
|
||||
let interact = self.reserve_space(
|
||||
vec2(
|
||||
self.available_width(),
|
||||
text_size.y + 2.0 * self.style.button_padding.y,
|
||||
),
|
||||
Some(id),
|
||||
);
|
||||
|
||||
let state = {
|
||||
let mut memory = self.ctx.memory.lock();
|
||||
let mut state = memory.foldables.entry(id).or_default();
|
||||
if interact.clicked {
|
||||
state.open = !state.open;
|
||||
state.toggle_time = self.ctx.input.time;
|
||||
}
|
||||
*state
|
||||
};
|
||||
|
||||
let fill_color = self.style.interact_fill_color(&interact);
|
||||
let stroke_color = self.style.interact_stroke_color(&interact);
|
||||
|
||||
self.add_paint_cmd(PaintCmd::Rect {
|
||||
corner_radius: self.style.interaction_corner_radius,
|
||||
fill_color,
|
||||
outline: Some(Outline::new(1.0, color::WHITE)),
|
||||
rect: interact.rect,
|
||||
});
|
||||
|
||||
let (small_icon_rect, _) = self.style.icon_rectangles(&interact.rect);
|
||||
// Draw a minus:
|
||||
self.add_paint_cmd(PaintCmd::Line {
|
||||
points: vec![
|
||||
pos2(small_icon_rect.min().x, small_icon_rect.center().y),
|
||||
pos2(small_icon_rect.max().x, small_icon_rect.center().y),
|
||||
],
|
||||
color: stroke_color,
|
||||
width: self.style.line_width,
|
||||
});
|
||||
|
||||
if !state.open {
|
||||
// Draw it as a plus:
|
||||
self.add_paint_cmd(PaintCmd::Line {
|
||||
points: vec![
|
||||
pos2(small_icon_rect.center().x, small_icon_rect.min().y),
|
||||
pos2(small_icon_rect.center().x, small_icon_rect.max().y),
|
||||
],
|
||||
color: stroke_color,
|
||||
width: self.style.line_width,
|
||||
});
|
||||
}
|
||||
|
||||
self.add_text(
|
||||
text_cursor + vec2(self.style.start_icon_width, 0.0),
|
||||
text_style,
|
||||
text,
|
||||
None,
|
||||
);
|
||||
|
||||
let animation_time = self.style().animation_time;
|
||||
let time_since_toggle = (self.ctx.input.time - state.toggle_time) as f32;
|
||||
if time_since_toggle < animation_time {
|
||||
self.indent(id, |region| {
|
||||
// animation time
|
||||
|
||||
let max_height = if state.open {
|
||||
remap(
|
||||
time_since_toggle,
|
||||
0.0,
|
||||
animation_time,
|
||||
50.0, // Get instant feedback
|
||||
1500.0, // We don't expect to get bigger than this
|
||||
)
|
||||
} else {
|
||||
remap_clamp(
|
||||
time_since_toggle,
|
||||
0.0,
|
||||
animation_time,
|
||||
50.0, // TODO: state.open_height
|
||||
0.0,
|
||||
)
|
||||
};
|
||||
|
||||
region
|
||||
.clip_rect
|
||||
.set_height(region.clip_rect.height().min(max_height));
|
||||
|
||||
add_contents(region);
|
||||
|
||||
region.bounding_size.y = region.bounding_size.y.min(max_height);
|
||||
});
|
||||
} else if state.open {
|
||||
self.indent(id, add_contents);
|
||||
}
|
||||
|
||||
self.response(interact)
|
||||
}
|
||||
|
||||
/// Create a child region which is indented to the right
|
||||
pub fn indent<F>(&mut self, id: Id, add_contents: F)
|
||||
where
|
||||
@@ -453,6 +341,14 @@ impl Region {
|
||||
self.add(Label::new(text))
|
||||
}
|
||||
|
||||
pub fn collapsing<S, F>(&mut self, text: S, add_contents: F) -> GuiResponse
|
||||
where
|
||||
S: Into<String>,
|
||||
F: FnOnce(&mut Region),
|
||||
{
|
||||
CollapsingHeader::new(text).show(self, add_contents)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
pub fn reserve_space(&mut self, size: Vec2, interaction_id: Option<Id>) -> InteractInfo {
|
||||
|
||||
@@ -11,13 +11,13 @@ pub struct Style {
|
||||
/// Horizontal and vertical spacing between widgets
|
||||
pub item_spacing: Vec2,
|
||||
|
||||
/// Indent foldable regions etc by this much.
|
||||
/// Indent collapsing regions etc by this much.
|
||||
pub indent: f32,
|
||||
|
||||
/// Anything clickable is (at least) this wide.
|
||||
pub clickable_diameter: f32,
|
||||
|
||||
/// Checkboxes, radio button and foldables have an icon at the start.
|
||||
/// Checkboxes, radio button and collapsing headers have an icon at the start.
|
||||
/// The text starts after this many pixels.
|
||||
pub start_icon_width: f32,
|
||||
|
||||
@@ -108,11 +108,29 @@ impl Style {
|
||||
}
|
||||
}
|
||||
|
||||
/// For rectangles
|
||||
pub fn interact_outline(&self, interact: &InteractInfo) -> Option<Outline> {
|
||||
if interact.active {
|
||||
Some(Outline::new(
|
||||
self.interact_stroke_width(interact),
|
||||
self.interact_stroke_color(interact),
|
||||
))
|
||||
} else if interact.hovered {
|
||||
None
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Buttons etc
|
||||
pub fn interact_corner_radius(&self, interact: &InteractInfo) -> f32 {
|
||||
if interact.active {
|
||||
5.0
|
||||
} else if interact.hovered {
|
||||
5.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns small icon rectangle and big icon rectangle
|
||||
|
||||
Reference in New Issue
Block a user