Improve size negotiation code.
Better enfocred minimum sizes. You can now have windows that expand to fit their content.
This commit is contained in:
parent
7cd8ac2bbf
commit
b73fbb33d8
11 changed files with 332 additions and 159 deletions
|
@ -98,6 +98,20 @@ impl Resize {
|
|||
self
|
||||
}
|
||||
|
||||
/// true: prevent from resizing to smaller than contents.
|
||||
/// false: allow shrinking to smaller than contents.
|
||||
pub fn auto_expand_width(mut self, auto_expand: bool) -> Self {
|
||||
self.expand_width_to_fit_content = auto_expand;
|
||||
self
|
||||
}
|
||||
|
||||
/// true: prevent from resizing to smaller than contents.
|
||||
/// false: allow shrinking to smaller than contents.
|
||||
pub fn auto_expand_height(mut self, auto_expand: bool) -> Self {
|
||||
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;
|
||||
|
@ -200,7 +214,7 @@ impl Resize {
|
|||
// state.size = state.size.clamp(self.min_size..=self.max_size);
|
||||
state.size = state.size.round(); // TODO: round to pixels
|
||||
|
||||
region.reserve_space_without_padding(state.size);
|
||||
region.reserve_space(state.size, None);
|
||||
|
||||
// ------------------------------
|
||||
|
||||
|
|
|
@ -165,8 +165,12 @@ impl ScrollArea {
|
|||
});
|
||||
}
|
||||
|
||||
let size = content_size.min(inner_rect.size());
|
||||
outer_region.reserve_space_without_padding(size);
|
||||
// let size = content_size.min(inner_rect.size());
|
||||
let size = vec2(
|
||||
content_size.x, // ignore inner_rect, i.e. try to expand horizontally if necessary
|
||||
content_size.y.min(inner_rect.size().y), // respect vertical height.
|
||||
);
|
||||
outer_region.reserve_space(size, None);
|
||||
|
||||
state.offset.y = state.offset.y.min(content_size.y - inner_rect.height());
|
||||
state.offset.y = state.offset.y.max(0.0);
|
||||
|
|
|
@ -7,11 +7,11 @@ use super::*;
|
|||
/// A wrapper around other containers for things you often want in a window
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Window {
|
||||
title: String,
|
||||
floating: Floating,
|
||||
frame: Frame,
|
||||
resize: Resize,
|
||||
scroll: ScrollArea,
|
||||
pub title: String,
|
||||
pub floating: Floating,
|
||||
pub frame: Frame,
|
||||
pub resize: Resize,
|
||||
pub scroll: ScrollArea,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
|
@ -23,14 +23,30 @@ impl Window {
|
|||
frame: Frame::default(),
|
||||
resize: Resize::default()
|
||||
.handle_offset(Vec2::splat(4.0))
|
||||
.auto_shrink_width(true)
|
||||
.auto_expand_width(true)
|
||||
.auto_shrink_height(false)
|
||||
.auto_expand(false),
|
||||
.auto_expand_height(false),
|
||||
scroll: ScrollArea::default()
|
||||
.always_show_scroll(false)
|
||||
.max_height(f32::INFINITY), // As large as we can be
|
||||
}
|
||||
}
|
||||
|
||||
/// This is quite a crap idea
|
||||
/// Usage: `Winmdow::new(...).mutate(|w| w.resize = w.resize.auto_expand_width(true))`
|
||||
pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self {
|
||||
mutate(&mut self);
|
||||
self
|
||||
}
|
||||
|
||||
/// This is quite a crap idea
|
||||
/// Usage: `Winmdow::new(...).resize(|r| r.auto_expand_width(true))`
|
||||
pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self {
|
||||
self.resize = mutate(self.resize);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn default_pos(mut self, default_pos: Pos2) -> Self {
|
||||
self.floating = self.floating.default_pos(default_pos);
|
||||
self
|
||||
|
|
|
@ -56,6 +56,14 @@ impl Context {
|
|||
(point * self.input.pixels_per_point).round() / self.input.pixels_per_point
|
||||
}
|
||||
|
||||
pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
|
||||
pos2(self.round_to_pixel(pos.x), self.round_to_pixel(pos.y))
|
||||
}
|
||||
|
||||
pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
|
||||
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
|
||||
}
|
||||
|
||||
/// Raw input from last frame. Use `input()` instead.
|
||||
pub fn last_raw_input(&self) -> &RawInput {
|
||||
&self.last_raw_input
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// #![allow(dead_code, unused_variables)] // should be commented out
|
||||
|
||||
use crate::{color::*, containers::*, widgets::*, *};
|
||||
|
||||
/// Showcase some region code
|
||||
pub struct ExampleApp {
|
||||
pub struct ExampleWindow {
|
||||
checked: bool,
|
||||
count: usize,
|
||||
radio: usize,
|
||||
|
@ -19,9 +21,9 @@ pub struct ExampleApp {
|
|||
painting: Painting,
|
||||
}
|
||||
|
||||
impl Default for ExampleApp {
|
||||
fn default() -> ExampleApp {
|
||||
ExampleApp {
|
||||
impl Default for ExampleWindow {
|
||||
fn default() -> ExampleWindow {
|
||||
ExampleWindow {
|
||||
checked: true,
|
||||
radio: 0,
|
||||
count: 0,
|
||||
|
@ -41,7 +43,7 @@ impl Default for ExampleApp {
|
|||
}
|
||||
}
|
||||
|
||||
impl ExampleApp {
|
||||
impl ExampleWindow {
|
||||
pub fn ui(&mut self, region: &mut Region) {
|
||||
region.collapsing("About Emigui", |region| {
|
||||
region.add(label!(
|
||||
|
|
|
@ -29,6 +29,14 @@ impl TextFragment {
|
|||
}
|
||||
}
|
||||
|
||||
// pub fn fn_text_width(fragmens: &[TextFragment]) -> f32 {
|
||||
// if fragmens.is_empty() {
|
||||
// 0.0
|
||||
// } else {
|
||||
// fragmens.last().unwrap().max_x() - fragmens.first().unwrap().min_x()
|
||||
// }
|
||||
// }
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
@ -93,6 +101,27 @@ impl Font {
|
|||
font
|
||||
}
|
||||
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||
}
|
||||
|
||||
/// Height of one line of text. In points
|
||||
/// TODO: rename height ?
|
||||
pub fn line_spacing(&self) -> f32 {
|
||||
self.scale_in_pixels / self.pixels_per_point
|
||||
}
|
||||
pub fn height(&self) -> f32 {
|
||||
self.scale_in_pixels / self.pixels_per_point
|
||||
}
|
||||
|
||||
pub fn uv_rect(&self, c: char) -> Option<UvRect> {
|
||||
self.glyph_infos.get(&c).and_then(|gi| gi.uv_rect)
|
||||
}
|
||||
|
||||
fn glyph_info(&self, c: char) -> Option<&GlyphInfo> {
|
||||
self.glyph_infos.get(&c)
|
||||
}
|
||||
|
||||
fn add_char(&mut self, c: char) {
|
||||
let glyph = self.font.glyph(c);
|
||||
assert_ne!(
|
||||
|
@ -154,26 +183,10 @@ impl Font {
|
|||
);
|
||||
}
|
||||
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||
}
|
||||
|
||||
/// Height of one line of text. In points
|
||||
/// TODO: rename height ?
|
||||
pub fn line_spacing(&self) -> f32 {
|
||||
self.scale_in_pixels / self.pixels_per_point
|
||||
}
|
||||
|
||||
pub fn uv_rect(&self, c: char) -> Option<UvRect> {
|
||||
self.glyph_infos.get(&c).and_then(|gi| gi.uv_rect)
|
||||
}
|
||||
|
||||
fn glyph_info(&self, c: char) -> Option<&GlyphInfo> {
|
||||
self.glyph_infos.get(&c)
|
||||
}
|
||||
|
||||
/// Returns the a single line of characters separated into words
|
||||
pub fn layout_single_line(&self, text: &str) -> Vec<TextFragment> {
|
||||
/// Always returns at least one frament. TODO: Vec1
|
||||
/// Returns total size.
|
||||
pub fn layout_single_line(&self, text: &str) -> (Vec<TextFragment>, Vec2) {
|
||||
let scale_in_pixels = Scale::uniform(self.scale_in_pixels);
|
||||
|
||||
let mut current_fragment = TextFragment {
|
||||
|
@ -220,7 +233,16 @@ impl Font {
|
|||
if !current_fragment.text.is_empty() {
|
||||
all_fragments.push(current_fragment)
|
||||
}
|
||||
all_fragments
|
||||
|
||||
let width = if all_fragments.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
all_fragments.last().unwrap().max_x()
|
||||
};
|
||||
|
||||
let size = vec2(width, self.height());
|
||||
|
||||
(all_fragments, size)
|
||||
}
|
||||
|
||||
/// A paragraph is text with no line break character in it.
|
||||
|
@ -229,8 +251,8 @@ impl Font {
|
|||
text: &str,
|
||||
max_width_in_points: f32,
|
||||
) -> Vec<TextFragment> {
|
||||
let mut words = self.layout_single_line(text);
|
||||
if words.is_empty() || words.last().unwrap().max_x() <= max_width_in_points {
|
||||
let (mut words, size) = self.layout_single_line(text);
|
||||
if words.is_empty() || size.x <= max_width_in_points {
|
||||
return words; // Early-out
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ use crate::{color::*, containers::*, font::TextFragment, layout::*, widgets::*,
|
|||
/// TODO: make Region a trait so we can have type-safe HorizontalRegion etc?
|
||||
pub struct Region {
|
||||
// TODO: remove pub(crate) from all members.
|
||||
//
|
||||
/// How we access input, output and memory
|
||||
pub(crate) ctx: Arc<Context>,
|
||||
|
||||
|
@ -48,6 +47,8 @@ pub struct Region {
|
|||
/// Where the next widget will be put.
|
||||
/// Progresses along self.dir.
|
||||
/// Initially set to rect.min
|
||||
/// If something has already been added, this will point ot style.item_spacing beyond the latest child.
|
||||
/// The cursor can thus be style.item_spacing pixels outside of the child_bounds.
|
||||
pub(crate) cursor: Pos2,
|
||||
}
|
||||
|
||||
|
@ -55,6 +56,9 @@ pub struct Region {
|
|||
const CLIP_RECT_MARGIN: f32 = 3.0;
|
||||
|
||||
impl Region {
|
||||
// ------------------------------------------------------------------------
|
||||
// Creation:
|
||||
|
||||
pub fn new(ctx: Arc<Context>, layer: Layer, id: Id, rect: Rect) -> Self {
|
||||
let style = ctx.style();
|
||||
Region {
|
||||
|
@ -126,6 +130,14 @@ impl Region {
|
|||
self.ctx.round_to_pixel(point)
|
||||
}
|
||||
|
||||
pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
|
||||
self.ctx.round_vec_to_pixels(vec)
|
||||
}
|
||||
|
||||
pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
|
||||
self.ctx.round_pos_to_pixels(pos)
|
||||
}
|
||||
|
||||
/// Options for this region, and any child regions we may spawn.
|
||||
pub fn style(&self) -> &Style {
|
||||
&self.style
|
||||
|
@ -158,15 +170,6 @@ impl Region {
|
|||
self.clip_rect
|
||||
}
|
||||
|
||||
/// This how much more space we can take up without overflowing our parent.
|
||||
/// Shrinks as cursor increments.
|
||||
pub fn available_space(&self) -> Vec2 {
|
||||
// self.desired_rect.max - self.cursor
|
||||
|
||||
// If a child doesn't fit in desired_rect, we have effectively expanded:
|
||||
self.bottom_right() - self.cursor
|
||||
}
|
||||
|
||||
pub fn bottom_right(&self) -> Pos2 {
|
||||
// If a child doesn't fit in desired_rect, we have effectively expanded:
|
||||
self.desired_rect.max.max(self.child_bounds.max)
|
||||
|
@ -180,6 +183,15 @@ impl Region {
|
|||
self.available_space().y
|
||||
}
|
||||
|
||||
/// This how much more space we can take up without overflowing our parent.
|
||||
/// Shrinks as cursor increments.
|
||||
pub fn available_space(&self) -> Vec2 {
|
||||
// self.desired_rect.max - self.cursor
|
||||
|
||||
// If a child doesn't fit in desired_rect, we have effectively expanded:
|
||||
self.bottom_right() - self.cursor
|
||||
}
|
||||
|
||||
/// Size of content
|
||||
pub fn bounding_size(&self) -> Vec2 {
|
||||
self.child_bounds.max - self.desired_rect.min
|
||||
|
@ -197,6 +209,60 @@ impl Region {
|
|||
self.align = align;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// Will warn if the returned id is not guaranteed unique.
|
||||
/// Use this to generate widget ids for widgets that have persistent state in Memory.
|
||||
/// If the id_source is not unique within this region
|
||||
/// then an error will be printed at the current cursor position.
|
||||
pub fn make_unique_id<IdSource>(&self, id_source: &IdSource) -> Id
|
||||
where
|
||||
IdSource: Hash + std::fmt::Debug,
|
||||
{
|
||||
let id = self.id.with(id_source);
|
||||
self.ctx.register_unique_id(id, id_source, self.cursor)
|
||||
}
|
||||
|
||||
/// Make an Id that is unique to this positon.
|
||||
/// Can be used for widgets that do NOT persist state in Memory
|
||||
/// but you still need to interact with (e.g. buttons, sliders).
|
||||
pub fn make_position_id(&self) -> Id {
|
||||
self.id.with(&Id::from_pos(self.cursor))
|
||||
}
|
||||
|
||||
pub fn make_child_id(&self, id_seed: impl Hash) -> Id {
|
||||
self.id.with(id_seed)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Interaction
|
||||
|
||||
/// Check for clicks on this entire region (desired_rect)
|
||||
pub fn interact_whole(&self) -> InteractInfo {
|
||||
self.ctx.interact(
|
||||
self.layer,
|
||||
&self.clip_rect,
|
||||
&self.desired_rect,
|
||||
Some(self.id),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn interact_rect(&self, rect: &Rect, id: Id) -> InteractInfo {
|
||||
self.ctx
|
||||
.interact(self.layer, &self.clip_rect, rect, Some(id))
|
||||
}
|
||||
|
||||
pub fn response(&mut self, interact: InteractInfo) -> GuiResponse {
|
||||
// TODO: unify GuiResponse and InteractInfo. They are the same thing!
|
||||
GuiResponse {
|
||||
hovered: interact.hovered,
|
||||
clicked: interact.clicked,
|
||||
active: interact.active,
|
||||
rect: interact.rect,
|
||||
ctx: self.ctx.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Sub-regions:
|
||||
|
||||
|
@ -214,7 +280,7 @@ impl Region {
|
|||
..self.child_region(child_rect)
|
||||
};
|
||||
add_contents(&mut child_region);
|
||||
self.reserve_space_without_padding(child_region.bounding_size());
|
||||
self.reserve_space(child_region.bounding_size(), None);
|
||||
}
|
||||
|
||||
/// Create a child region which is indented to the right
|
||||
|
@ -243,7 +309,7 @@ impl Region {
|
|||
width: self.style.line_width,
|
||||
});
|
||||
|
||||
self.reserve_space_without_padding(indent + size);
|
||||
self.reserve_space(indent + size, None);
|
||||
}
|
||||
|
||||
pub fn left_column(&mut self, width: f32) -> Region {
|
||||
|
@ -271,6 +337,17 @@ impl Region {
|
|||
))
|
||||
}
|
||||
|
||||
/// Start a region with horizontal layout
|
||||
// TODO: remove first argument
|
||||
pub fn horizontal(&mut self, align: Align, add_contents: impl FnOnce(&mut Region)) {
|
||||
self.inner_layout(Direction::Horizontal, align, add_contents)
|
||||
}
|
||||
|
||||
/// Start a region with vertical layout
|
||||
pub fn vertical(&mut self, align: Align, add_contents: impl FnOnce(&mut Region)) {
|
||||
self.inner_layout(Direction::Vertical, align, add_contents)
|
||||
}
|
||||
|
||||
pub fn inner_layout(
|
||||
&mut self,
|
||||
dir: Direction,
|
||||
|
@ -288,16 +365,6 @@ impl Region {
|
|||
self.reserve_space(size, None);
|
||||
}
|
||||
|
||||
/// Start a region with horizontal layout
|
||||
pub fn horizontal(&mut self, align: Align, add_contents: impl FnOnce(&mut Region)) {
|
||||
self.inner_layout(Direction::Horizontal, align, add_contents)
|
||||
}
|
||||
|
||||
/// Start a region with vertical layout
|
||||
pub fn vertical(&mut self, align: Align, add_contents: impl FnOnce(&mut Region)) {
|
||||
self.inner_layout(Direction::Vertical, align, add_contents)
|
||||
}
|
||||
|
||||
/// Temporarily split split a vertical layout into several columns.
|
||||
///
|
||||
/// region.columns(2, |columns| {
|
||||
|
@ -309,13 +376,13 @@ impl Region {
|
|||
F: FnOnce(&mut [Region]) -> R,
|
||||
{
|
||||
// TODO: ensure there is space
|
||||
let padding = self.style.item_spacing.x;
|
||||
let total_padding = padding * (num_columns as f32 - 1.0);
|
||||
let column_width = (self.available_width() - total_padding) / (num_columns as f32);
|
||||
let spacing = self.style.item_spacing.x;
|
||||
let total_spacing = spacing * (num_columns as f32 - 1.0);
|
||||
let column_width = (self.available_width() - total_spacing) / (num_columns as f32);
|
||||
|
||||
let mut columns: Vec<Region> = (0..num_columns)
|
||||
.map(|col_idx| {
|
||||
let pos = self.cursor + vec2((col_idx as f32) * (column_width + padding), 0.0);
|
||||
let pos = self.cursor + vec2((col_idx as f32) * (column_width + spacing), 0.0);
|
||||
let child_rect =
|
||||
Rect::from_min_max(pos, pos2(pos.x + column_width, self.bottom_right().y));
|
||||
|
||||
|
@ -329,33 +396,24 @@ impl Region {
|
|||
|
||||
let result = add_contents(&mut columns[..]);
|
||||
|
||||
let mut sum_width = total_spacing;
|
||||
for column in &columns {
|
||||
sum_width += column.child_bounds.width();
|
||||
}
|
||||
|
||||
let mut max_height = 0.0;
|
||||
for region in columns {
|
||||
let size = region.bounding_size();
|
||||
max_height = size.y.max(max_height);
|
||||
}
|
||||
|
||||
self.reserve_space_without_padding(vec2(self.available_width(), max_height));
|
||||
let size = vec2(self.available_width().max(sum_width), max_height);
|
||||
self.reserve_space(size, None);
|
||||
result
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// Check for clicks on this entire region (desired_rect)
|
||||
pub fn interact_whole(&self) -> InteractInfo {
|
||||
self.ctx.interact(
|
||||
self.layer,
|
||||
&self.clip_rect,
|
||||
&self.desired_rect,
|
||||
Some(self.id),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn interact_rect(&self, rect: &Rect, id: Id) -> InteractInfo {
|
||||
self.ctx
|
||||
.interact(self.layer, &self.clip_rect, rect, Some(id))
|
||||
}
|
||||
|
||||
pub fn contains_mouse(&self, rect: &Rect) -> bool {
|
||||
self.ctx.contains_mouse(self.layer, &self.clip_rect, rect)
|
||||
}
|
||||
|
@ -393,65 +451,94 @@ impl Region {
|
|||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Stuff that moves the cursor, i.e. allocates space in this region!
|
||||
|
||||
/// Reserve this much space and move the cursor.
|
||||
/// Returns where to put the widget.
|
||||
/// # How sizes are negotiated
|
||||
/// Each widget should have a *minimum desired size* and a *desired size*.
|
||||
/// When asking for space, ask AT LEAST for you minimum, and don't ask for more than you need.
|
||||
/// If you want to fill the space, ask about available_space() and use that.
|
||||
/// NOTE: we always get the size we ask for (at the moment).
|
||||
pub fn reserve_space(&mut self, child_size: Vec2, interaction_id: Option<Id>) -> InteractInfo {
|
||||
let child_size = self.round_vec_to_pixels(child_size);
|
||||
self.cursor = self.round_pos_to_pixels(self.cursor);
|
||||
|
||||
// For debug rendering
|
||||
let too_wide = child_size.x > self.available_width();
|
||||
let too_high = child_size.x > self.available_height();
|
||||
|
||||
let child_pos = self.reserve_space_impl(child_size);
|
||||
let rect = Rect::from_min_size(child_pos, child_size);
|
||||
|
||||
if self.style().debug_regions {
|
||||
self.add_paint_cmd(PaintCmd::Rect {
|
||||
rect,
|
||||
corner_radius: 0.0,
|
||||
outline: Some(Outline::new(1.0, LIGHT_BLUE)),
|
||||
fill_color: None,
|
||||
});
|
||||
|
||||
let color = color::srgba(255, 0, 0, 128);
|
||||
let width = 2.5;
|
||||
|
||||
if too_wide {
|
||||
self.add_paint_cmd(PaintCmd::line_segment(
|
||||
(rect.left_top(), rect.left_bottom()),
|
||||
color,
|
||||
width,
|
||||
));
|
||||
self.add_paint_cmd(PaintCmd::line_segment(
|
||||
(rect.right_top(), rect.right_bottom()),
|
||||
color,
|
||||
width,
|
||||
));
|
||||
}
|
||||
|
||||
if too_high {
|
||||
self.add_paint_cmd(PaintCmd::line_segment(
|
||||
(rect.left_top(), rect.right_top()),
|
||||
color,
|
||||
width,
|
||||
));
|
||||
self.add_paint_cmd(PaintCmd::line_segment(
|
||||
(rect.left_bottom(), rect.right_bottom()),
|
||||
color,
|
||||
width,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reserve_space(&mut self, size: Vec2, interaction_id: Option<Id>) -> InteractInfo {
|
||||
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, &self.clip_rect, &rect, interaction_id)
|
||||
}
|
||||
|
||||
/// Reserve this much space and move the cursor.
|
||||
/// Returns where to put the widget.
|
||||
pub fn reserve_space_without_padding(&mut self, size: Vec2) -> Pos2 {
|
||||
let mut pos = self.cursor;
|
||||
fn reserve_space_impl(&mut self, child_size: Vec2) -> Pos2 {
|
||||
let mut child_pos = self.cursor;
|
||||
if self.dir == Direction::Horizontal {
|
||||
pos.y += match self.align {
|
||||
child_pos.y += match self.align {
|
||||
Align::Min => 0.0,
|
||||
Align::Center => 0.5 * (self.available_height() - size.y),
|
||||
Align::Max => self.available_height() - size.y,
|
||||
Align::Center => 0.5 * (self.available_height() - child_size.y),
|
||||
Align::Max => self.available_height() - child_size.y,
|
||||
};
|
||||
self.child_bounds.extend_with(self.cursor + size);
|
||||
self.cursor.x += size.x;
|
||||
self.child_bounds.extend_with(self.cursor + child_size);
|
||||
self.cursor.x += child_size.x;
|
||||
self.cursor.x += self.style.item_spacing.x; // Where to put next thing, if there is a next thing
|
||||
} else {
|
||||
pos.x += match self.align {
|
||||
child_pos.x += match self.align {
|
||||
Align::Min => 0.0,
|
||||
Align::Center => 0.5 * (self.available_width() - size.x),
|
||||
Align::Max => self.available_width() - size.x,
|
||||
Align::Center => 0.5 * (self.available_width() - child_size.x),
|
||||
Align::Max => self.available_width() - child_size.x,
|
||||
};
|
||||
self.child_bounds.extend_with(self.cursor + size);
|
||||
self.cursor.y += size.y;
|
||||
self.child_bounds.extend_with(self.cursor + child_size);
|
||||
self.cursor.y += child_size.y;
|
||||
self.cursor.y += self.style.item_spacing.y; // Where to put next thing, if there is a next thing
|
||||
}
|
||||
pos
|
||||
}
|
||||
|
||||
/// Will warn if the returned id is not guaranteed unique.
|
||||
/// Use this to generate widget ids for widgets that have persistent state in Memory.
|
||||
/// If the id_source is not unique within this region
|
||||
/// then an error will be printed at the current cursor position.
|
||||
pub fn make_unique_id<IdSource>(&self, id_source: &IdSource) -> Id
|
||||
where
|
||||
IdSource: Hash + std::fmt::Debug,
|
||||
{
|
||||
let id = self.id.with(id_source);
|
||||
self.ctx.register_unique_id(id, id_source, self.cursor)
|
||||
child_pos
|
||||
}
|
||||
|
||||
/// Make an Id that is unique to this positon.
|
||||
/// Can be used for widgets that do NOT persist state in Memory
|
||||
/// but you still need to interact with (e.g. buttons, sliders).
|
||||
pub fn make_position_id(&self) -> Id {
|
||||
self.id.with(&Id::from_pos(self.cursor))
|
||||
}
|
||||
|
||||
pub fn make_child_id(&self, id_seed: impl Hash) -> Id {
|
||||
self.id.with(id_seed)
|
||||
}
|
||||
|
||||
// ------------------------------------------------
|
||||
|
||||
/// Paint some debug text at current cursor
|
||||
|
@ -497,15 +584,4 @@ impl Region {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn response(&mut self, interact: InteractInfo) -> GuiResponse {
|
||||
// TODO: unify GuiResponse and InteractInfo. They are the same thing!
|
||||
GuiResponse {
|
||||
hovered: interact.hovered,
|
||||
clicked: interact.clicked,
|
||||
active: interact.active,
|
||||
rect: interact.rect,
|
||||
ctx: self.ctx.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,10 @@ pub struct Style {
|
|||
pub animation_time: f32,
|
||||
|
||||
pub window: Window,
|
||||
|
||||
// -----------------------------------------------
|
||||
// Debug rendering:
|
||||
pub debug_regions: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
|
@ -57,6 +61,7 @@ impl Default for Style {
|
|||
text_cursor_width: 2.0,
|
||||
animation_time: 1.0 / 20.0,
|
||||
window: Window::default(),
|
||||
debug_regions: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,22 +159,28 @@ impl Style {
|
|||
impl Style {
|
||||
#[rustfmt::skip]
|
||||
pub fn ui(&mut self, region: &mut crate::Region) {
|
||||
use crate::widgets::{Button, Slider};
|
||||
use crate::{widgets::*, *};
|
||||
if region.add(Button::new("Reset style")).clicked {
|
||||
*self = Default::default();
|
||||
}
|
||||
|
||||
|
||||
region.add(Slider::f32(&mut self.item_spacing.x, 0.0..=10.0).text("item_spacing.x").precision(0));
|
||||
region.add(Slider::f32(&mut self.item_spacing.y, 0.0..=10.0).text("item_spacing.y").precision(0));
|
||||
region.add(Slider::f32(&mut self.window_padding.x, 0.0..=10.0).text("window_padding.x").precision(0));
|
||||
region.add(Slider::f32(&mut self.window_padding.y, 0.0..=10.0).text("window_padding.y").precision(0));
|
||||
region.add(Slider::f32(&mut self.indent, 0.0..=100.0).text("indent").precision(0));
|
||||
region.add(Slider::f32(&mut self.button_padding.x, 0.0..=20.0).text("button_padding.x").precision(0));
|
||||
region.add(Slider::f32(&mut self.button_padding.y, 0.0..=20.0).text("button_padding.y").precision(0));
|
||||
region.add(Slider::f32(&mut self.item_spacing.x, 0.0..=10.0).text("item_spacing.x").precision(0));
|
||||
region.add(Slider::f32(&mut self.item_spacing.y, 0.0..=10.0).text("item_spacing.y").precision(0));
|
||||
region.add(Slider::f32(&mut self.window_padding.x, 0.0..=10.0).text("window_padding.x").precision(0));
|
||||
region.add(Slider::f32(&mut self.window_padding.y, 0.0..=10.0).text("window_padding.y").precision(0));
|
||||
region.add(Slider::f32(&mut self.indent, 0.0..=100.0).text("indent").precision(0));
|
||||
region.add(Slider::f32(&mut self.button_padding.x, 0.0..=20.0).text("button_padding.x").precision(0));
|
||||
region.add(Slider::f32(&mut self.button_padding.y, 0.0..=20.0).text("button_padding.y").precision(0));
|
||||
region.add(Slider::f32(&mut self.clickable_diameter, 0.0..=60.0).text("clickable_diameter").precision(0));
|
||||
region.add(Slider::f32(&mut self.start_icon_width, 0.0..=60.0).text("start_icon_width").precision(0));
|
||||
region.add(Slider::f32(&mut self.line_width, 0.0..=10.0).text("line_width").precision(0));
|
||||
region.add(Slider::f32(&mut self.animation_time, 0.0..=1.0).text("animation_time").precision(2));
|
||||
region.add(Slider::f32(&mut self.start_icon_width, 0.0..=60.0).text("start_icon_width").precision(0));
|
||||
region.add(Slider::f32(&mut self.line_width, 0.0..=10.0).text("line_width").precision(0));
|
||||
region.add(Slider::f32(&mut self.animation_time, 0.0..=1.0).text("animation_time").precision(2));
|
||||
|
||||
|
||||
// TODO: region.section("Heading", |ui| ui.add(contents))
|
||||
region.add(Separator::new());
|
||||
region.add(label!("Debug:").text_style(TextStyle::Heading));
|
||||
region.add(Checkbox::new(&mut self.debug_regions, "debug_regions"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ pub trait Widget {
|
|||
|
||||
pub struct Label {
|
||||
text: String,
|
||||
multiline: bool,
|
||||
text_style: TextStyle, // TODO: Option<TextStyle>, where None means "use the default for the region"
|
||||
text_color: Option<Color>,
|
||||
}
|
||||
|
@ -29,11 +30,17 @@ impl Label {
|
|||
pub fn new(text: impl Into<String>) -> Self {
|
||||
Label {
|
||||
text: text.into(),
|
||||
multiline: true,
|
||||
text_style: TextStyle::Body,
|
||||
text_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn multiline(mut self, multiline: bool) -> Self {
|
||||
self.multiline = multiline;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
self.text_style = text_style;
|
||||
self
|
||||
|
@ -55,7 +62,11 @@ macro_rules! label {
|
|||
impl Widget for Label {
|
||||
fn ui(self, region: &mut Region) -> GuiResponse {
|
||||
let font = ®ion.fonts()[self.text_style];
|
||||
let (text, text_size) = font.layout_multiline(&self.text, region.available_width());
|
||||
let (text, text_size) = if self.multiline {
|
||||
font.layout_multiline(&self.text, region.available_width())
|
||||
} else {
|
||||
font.layout_single_line(&self.text)
|
||||
};
|
||||
let interact = region.reserve_space(text_size, None);
|
||||
region.add_text(interact.rect.min, self.text_style, text, self.text_color);
|
||||
region.response(interact)
|
||||
|
@ -423,8 +434,9 @@ impl<'a> Widget for Slider<'a> {
|
|||
let slider_sans_text = Slider { text: None, ..self };
|
||||
|
||||
if text_on_top {
|
||||
let (text, text_size) = font.layout_multiline(&full_text, region.available_width());
|
||||
let pos = region.reserve_space_without_padding(text_size);
|
||||
// let (text, text_size) = font.layout_multiline(&full_text, region.available_width());
|
||||
let (text, text_size) = font.layout_single_line(&full_text);
|
||||
let pos = region.reserve_space(text_size, None).rect.min;
|
||||
region.add_text(pos, text_style, text, text_color);
|
||||
slider_sans_text.ui(region)
|
||||
} else {
|
||||
|
@ -437,7 +449,7 @@ impl<'a> Widget for Slider<'a> {
|
|||
.desired_rect
|
||||
.set_height(slider_response.rect.height());
|
||||
columns[1].horizontal(Align::Center, |region| {
|
||||
region.add(Label::new(full_text));
|
||||
region.add(Label::new(full_text).multiline(false));
|
||||
});
|
||||
|
||||
slider_response
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
#![deny(warnings)]
|
||||
#[allow(clippy::single_match)]
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use {
|
||||
emigui::{containers::*, example_app::ExampleApp, widgets::*, *},
|
||||
emigui::{containers::*, example_app::ExampleWindow, widgets::*, *},
|
||||
glium::glutin,
|
||||
};
|
||||
|
||||
|
@ -39,8 +42,8 @@ fn main() {
|
|||
let start_time = Instant::now();
|
||||
let mut running = true;
|
||||
let mut frame_start = Instant::now();
|
||||
let mut frame_times = std::collections::VecDeque::new();
|
||||
let mut example_app = ExampleApp::default();
|
||||
let mut frame_times = VecDeque::new();
|
||||
let mut example_app = ExampleWindow::default();
|
||||
let mut clipboard = emigui_glium::init_clipboard();
|
||||
|
||||
while running {
|
||||
|
@ -74,15 +77,10 @@ fn main() {
|
|||
running = false;
|
||||
}
|
||||
|
||||
let mean_frame_time = if frame_times.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
frame_times.iter().sum::<f64>() / (frame_times.len() as f64)
|
||||
};
|
||||
region.add(
|
||||
label!(
|
||||
"Frame time: {:.1} ms (excludes painting)",
|
||||
1e3 * mean_frame_time
|
||||
1e3 * mean_frame_time(&frame_times)
|
||||
)
|
||||
.text_style(TextStyle::Monospace),
|
||||
);
|
||||
|
@ -92,6 +90,8 @@ fn main() {
|
|||
Window::new("Examples")
|
||||
.default_pos(pos2(50.0, 100.0))
|
||||
.default_size(vec2(300.0, 600.0))
|
||||
// .mutate(|w| w.resize = w.resize.auto_expand_width(true))
|
||||
// .resize(|r| r.auto_expand_width(true))
|
||||
.show(region.ctx(), |region| {
|
||||
example_app.ui(region);
|
||||
});
|
||||
|
@ -114,3 +114,11 @@ fn main() {
|
|||
emigui_glium::handle_output(output, &display, clipboard.as_mut());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mean_frame_time(frame_times: &VecDeque<f64>) -> f64 {
|
||||
if frame_times.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
frame_times.iter().sum::<f64>() / (frame_times.len() as f64)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use {
|
|||
emigui::{
|
||||
color::srgba,
|
||||
containers::*,
|
||||
example_app::ExampleApp,
|
||||
example_app::ExampleWindow,
|
||||
label,
|
||||
widgets::{Label, Separator},
|
||||
Align, Emigui, RawInput, TextStyle, *,
|
||||
|
@ -22,7 +22,7 @@ use wasm_bindgen::prelude::*;
|
|||
#[wasm_bindgen]
|
||||
|
||||
pub struct State {
|
||||
example_app: ExampleApp,
|
||||
example_app: ExampleWindow,
|
||||
emigui: Emigui,
|
||||
webgl_painter: emigui_wasm::webgl::Painter,
|
||||
|
||||
|
|
Loading…
Reference in a new issue