Improve size negotiation code.

Better enfocred minimum sizes.
You can now have windows that expand to fit their content.
This commit is contained in:
Emil Ernerfeldt 2020-05-01 02:08:01 +02:00
parent 7cd8ac2bbf
commit b73fbb33d8
11 changed files with 332 additions and 159 deletions

View file

@ -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);
// ------------------------------

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = &region.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

View file

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

View file

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