Keep window title and collapsing headers on one line
This commit is contained in:
parent
570215df9a
commit
702e135f07
6 changed files with 114 additions and 52 deletions
|
@ -1,4 +1,4 @@
|
||||||
use crate::{layout::Direction, *};
|
use crate::{layout::Direction, widgets::Label, *};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, serde_derive::Deserialize, serde_derive::Serialize)]
|
#[derive(Clone, Copy, Debug, serde_derive::Deserialize, serde_derive::Serialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -23,14 +23,16 @@ impl Default for State {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CollapsingHeader {
|
pub struct CollapsingHeader {
|
||||||
title: String,
|
label: Label,
|
||||||
default_open: bool,
|
default_open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CollapsingHeader {
|
impl CollapsingHeader {
|
||||||
pub fn new(title: impl Into<String>) -> Self {
|
pub fn new(label: impl Into<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
title: title.into(),
|
label: Label::new(label)
|
||||||
|
.text_style(TextStyle::Button)
|
||||||
|
.multiline(false),
|
||||||
default_open: false,
|
default_open: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,22 +50,27 @@ impl CollapsingHeader {
|
||||||
"Horizontal collapsing is unimplemented"
|
"Horizontal collapsing is unimplemented"
|
||||||
);
|
);
|
||||||
let Self {
|
let Self {
|
||||||
title,
|
label,
|
||||||
default_open,
|
default_open,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let id = region.make_unique_id(&title);
|
// TODO: horizontal layout, with icon and text as labels. Inser background behind using Frame.
|
||||||
let text_style = TextStyle::Button;
|
|
||||||
let font = ®ion.fonts()[text_style];
|
let title = &label.text; // TODO: not this
|
||||||
let (title, text_size) = font.layout_multiline(&title, region.available_width());
|
let id = region.make_unique_id(title);
|
||||||
|
let text_pos = region.cursor() + vec2(region.style().indent, 0.0);
|
||||||
|
let (title, text_size) = label.layout(text_pos, region);
|
||||||
|
let text_max_x = text_pos.x + text_size.x;
|
||||||
|
let desired_width = region.available_width().max(text_max_x - region.cursor().x);
|
||||||
|
|
||||||
let interact = region.reserve_space(
|
let interact = region.reserve_space(
|
||||||
vec2(
|
vec2(
|
||||||
region.available_width(),
|
desired_width,
|
||||||
text_size.y + 2.0 * region.style().button_padding.y,
|
text_size.y + 2.0 * region.style().button_padding.y,
|
||||||
),
|
),
|
||||||
Some(id),
|
Some(id),
|
||||||
);
|
);
|
||||||
|
let text_pos = pos2(text_pos.x, interact.rect.center().y - text_size.y / 2.0);
|
||||||
|
|
||||||
let mut state = {
|
let mut state = {
|
||||||
let mut memory = region.memory();
|
let mut memory = region.memory();
|
||||||
|
@ -78,25 +85,29 @@ impl CollapsingHeader {
|
||||||
*state
|
*state
|
||||||
};
|
};
|
||||||
|
|
||||||
region.add_paint_cmd(PaintCmd::Rect {
|
let where_to_put_background = region.paint_list_len();
|
||||||
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);
|
paint_icon(region, &state, &interact);
|
||||||
|
|
||||||
region.add_text(
|
region.add_text(
|
||||||
pos2(
|
text_pos,
|
||||||
interact.rect.left() + region.style().indent,
|
label.text_style,
|
||||||
interact.rect.center().y - text_size.y / 2.0,
|
|
||||||
),
|
|
||||||
text_style,
|
|
||||||
title,
|
title,
|
||||||
Some(region.style().interact_stroke_color(&interact)),
|
Some(region.style().interact_stroke_color(&interact)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
region.insert_paint_cmd(
|
||||||
|
where_to_put_background,
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
region.expand_to_include_child(interact.rect); // TODO: remove, just a test
|
||||||
|
|
||||||
let animation_time = region.style().animation_time;
|
let animation_time = region.style().animation_time;
|
||||||
let time_since_toggle = (region.input().time - state.toggle_time) as f32;
|
let time_since_toggle = (region.input().time - state.toggle_time) as f32;
|
||||||
let time_since_toggle = time_since_toggle + region.input().dt; // Instant feedback
|
let time_since_toggle = time_since_toggle + region.input().dt; // Instant feedback
|
||||||
|
|
|
@ -3,12 +3,21 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Frame {}
|
pub struct Frame {
|
||||||
|
pub margin: Option<Vec2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
pub fn margin(mut self, margin: Vec2) -> Self {
|
||||||
|
self.margin = Some(margin);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
pub fn show(self, region: &mut Region, add_contents: impl FnOnce(&mut Region)) {
|
pub fn show(self, region: &mut Region, add_contents: impl FnOnce(&mut Region)) {
|
||||||
let style = region.style();
|
let style = region.style();
|
||||||
let margin = style.window_padding;
|
let margin = self.margin.unwrap_or_default();
|
||||||
|
|
||||||
let outer_pos = region.cursor();
|
let outer_pos = region.cursor();
|
||||||
let inner_rect =
|
let inner_rect =
|
||||||
|
|
|
@ -5,9 +5,8 @@ use crate::{widgets::*, *};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// A wrapper around other containers for things you often want in a window
|
/// A wrapper around other containers for things you often want in a window
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
pub title: String,
|
pub title_label: Label,
|
||||||
pub floating: Floating,
|
pub floating: Floating,
|
||||||
pub frame: Frame,
|
pub frame: Frame,
|
||||||
pub resize: Resize,
|
pub resize: Resize,
|
||||||
|
@ -15,11 +14,16 @@ pub struct Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
// TODO: Into<Label>
|
||||||
pub fn new(title: impl Into<String>) -> Self {
|
pub fn new(title: impl Into<String>) -> Self {
|
||||||
let title = title.into();
|
let title = title.into();
|
||||||
|
let floating = Floating::new(&title);
|
||||||
|
let title_label = Label::new(title)
|
||||||
|
.text_style(TextStyle::Heading)
|
||||||
|
.multiline(false);
|
||||||
Self {
|
Self {
|
||||||
title: title.clone(),
|
title_label,
|
||||||
floating: Floating::new(title),
|
floating,
|
||||||
frame: Frame::default(),
|
frame: Frame::default(),
|
||||||
resize: Resize::default()
|
resize: Resize::default()
|
||||||
.handle_offset(Vec2::splat(4.0))
|
.handle_offset(Vec2::splat(4.0))
|
||||||
|
@ -83,17 +87,19 @@ impl Window {
|
||||||
impl Window {
|
impl Window {
|
||||||
pub fn show(self, ctx: &Arc<Context>, add_contents: impl FnOnce(&mut Region)) {
|
pub fn show(self, ctx: &Arc<Context>, add_contents: impl FnOnce(&mut Region)) {
|
||||||
let Window {
|
let Window {
|
||||||
title,
|
title_label,
|
||||||
floating,
|
floating,
|
||||||
frame,
|
mut frame,
|
||||||
resize,
|
resize,
|
||||||
scroll,
|
scroll,
|
||||||
} = self;
|
} = self;
|
||||||
|
frame.margin = Some(frame.margin.unwrap_or_else(|| ctx.style().window_padding));
|
||||||
|
|
||||||
// TODO: easier way to compose these
|
// TODO: easier way to compose these
|
||||||
floating.show(ctx, |region| {
|
floating.show(ctx, |region| {
|
||||||
frame.show(region, |region| {
|
frame.show(region, |region| {
|
||||||
resize.show(region, |region| {
|
resize.show(region, |region| {
|
||||||
region.add(Label::new(title).text_style(TextStyle::Heading));
|
region.add(title_label);
|
||||||
region.add(Separator::new().line_width(1.0)); // TODO: nicer way to split window title from contents
|
region.add(Separator::new().line_width(1.0)); // TODO: nicer way to split window title from contents
|
||||||
scroll.show(region, |region| add_contents(region))
|
scroll.show(region, |region| add_contents(region))
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub struct Region {
|
||||||
/// Where to put the graphics output of this Region
|
/// Where to put the graphics output of this Region
|
||||||
layer: Layer,
|
layer: Layer,
|
||||||
|
|
||||||
/// Everything painte in this rect will be clipped against this.
|
/// Everything painted in this region will be clipped against this.
|
||||||
/// This means nothing outside of this rectangle will be visible on screen.
|
/// This means nothing outside of this rectangle will be visible on screen.
|
||||||
clip_rect: Rect,
|
clip_rect: Rect,
|
||||||
|
|
||||||
|
@ -28,12 +28,14 @@ pub struct Region {
|
||||||
/// Note that the size may be infinite in one or both dimensions.
|
/// Note that the size may be infinite in one or both dimensions.
|
||||||
/// The widgets will TRY to fit within the rect,
|
/// The widgets will TRY to fit within the rect,
|
||||||
/// but may overflow (which you will see in child_bounds).
|
/// but may overflow (which you will see in child_bounds).
|
||||||
desired_rect: Rect, // TODO: rename?
|
/// Some widgets (like separator lines) will try to fill the full desired width of the region.
|
||||||
|
desired_rect: Rect, // TODO: rename as max_rect ?
|
||||||
|
|
||||||
/// Bounding box of all children.
|
/// Bounding box of all children.
|
||||||
/// This is used to see how large a region actually
|
/// This is used to see how large a region actually
|
||||||
/// needs to be after all children has been added.
|
/// needs to be after all children has been added.
|
||||||
child_bounds: Rect,
|
/// You can think of this as the minimum size.
|
||||||
|
child_bounds: Rect, // TODO: rename as min_rect ?
|
||||||
|
|
||||||
/// Overide default style in this region
|
/// Overide default style in this region
|
||||||
style: Style,
|
style: Style,
|
||||||
|
@ -170,7 +172,7 @@ impl Region {
|
||||||
|
|
||||||
/// Set the width of the region.
|
/// Set the width of the region.
|
||||||
/// You won't be able to shrink it beyond its current child bounds.
|
/// You won't be able to shrink it beyond its current child bounds.
|
||||||
pub fn set_width(&mut self, width: f32) {
|
pub fn set_desired_width(&mut self, width: f32) {
|
||||||
let min_width = self.child_bounds.max.x - self.top_left().x;
|
let min_width = self.child_bounds.max.x - self.top_left().x;
|
||||||
let width = width.max(min_width);
|
let width = width.max(min_width);
|
||||||
self.desired_rect.max.x = self.top_left().x + width;
|
self.desired_rect.max.x = self.top_left().x + width;
|
||||||
|
@ -178,7 +180,7 @@ impl Region {
|
||||||
|
|
||||||
/// Set the height of the region.
|
/// Set the height of the region.
|
||||||
/// You won't be able to shrink it beyond its current child bounds.
|
/// You won't be able to shrink it beyond its current child bounds.
|
||||||
pub fn set_height(&mut self, height: f32) {
|
pub fn set_desired_height(&mut self, height: f32) {
|
||||||
let min_height = self.child_bounds.max.y - self.top_left().y;
|
let min_height = self.child_bounds.max.y - self.top_left().y;
|
||||||
let height = height.max(min_height);
|
let height = height.max(min_height);
|
||||||
self.desired_rect.max.y = self.top_left().y + height;
|
self.desired_rect.max.y = self.top_left().y + height;
|
||||||
|
@ -195,6 +197,10 @@ impl Region {
|
||||||
self.child_bounds.extend_with(rect.max);
|
self.child_bounds.extend_with(rect.max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expand_to_size(&mut self, size: Vec2) {
|
||||||
|
self.child_bounds.extend_with(self.top_left() + size);
|
||||||
|
}
|
||||||
|
|
||||||
/// Bounding box of all contained children
|
/// Bounding box of all contained children
|
||||||
pub fn child_bounds(&self) -> Rect {
|
pub fn child_bounds(&self) -> Rect {
|
||||||
self.child_bounds
|
self.child_bounds
|
||||||
|
@ -258,6 +264,7 @@ impl Region {
|
||||||
IdSource: Hash + std::fmt::Debug,
|
IdSource: Hash + std::fmt::Debug,
|
||||||
{
|
{
|
||||||
let id = self.id.with(id_source);
|
let id = self.id.with(id_source);
|
||||||
|
// TODO: clip name clash error messages to clip rect
|
||||||
self.ctx.register_unique_id(id, id_source, self.cursor)
|
self.ctx.register_unique_id(id, id_source, self.cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,6 +441,7 @@ impl Region {
|
||||||
self.debug_text_at(self.cursor, text);
|
self.debug_text_at(self.cursor, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: AsRef<str>
|
||||||
pub fn debug_text_at(&self, pos: Pos2, text: &str) {
|
pub fn debug_text_at(&self, pos: Pos2, text: &str) {
|
||||||
self.ctx.debug_text(pos, text);
|
self.ctx.debug_text(pos, text);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,11 @@ pub trait Widget {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
pub struct Label {
|
pub struct Label {
|
||||||
text: String,
|
// TODO: not pub
|
||||||
multiline: bool,
|
pub(crate) text: String,
|
||||||
text_style: TextStyle, // TODO: Option<TextStyle>, where None means "use the default for the region"
|
pub(crate) multiline: bool,
|
||||||
text_color: Option<Color>,
|
pub(crate) text_style: TextStyle, // TODO: Option<TextStyle>, where None means "use the default for the region"
|
||||||
|
pub(crate) text_color: Option<Color>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Label {
|
impl Label {
|
||||||
|
@ -50,6 +51,24 @@ impl Label {
|
||||||
self.text_color = Some(text_color);
|
self.text_color = Some(text_color);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn layout(&self, pos: Pos2, region: &Region) -> (Vec<font::TextFragment>, Vec2) {
|
||||||
|
let font = ®ion.fonts()[self.text_style];
|
||||||
|
let max_width = region.rect().right() - pos.x;
|
||||||
|
if self.multiline {
|
||||||
|
font.layout_multiline(&self.text, max_width)
|
||||||
|
} else {
|
||||||
|
font.layout_single_line(&self.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this should return a LabelLayout which has a paint method.
|
||||||
|
// We can then split Widget::Ui in two: layout + allocating space, and painting.
|
||||||
|
// this allows us to assemble lables, THEN detect interaction, THEN chose color style based on that.
|
||||||
|
// pub fn layout(self, region: &mut region) -> LabelLayout { }
|
||||||
|
|
||||||
|
// TODO: a paint method for painting anywhere in a region.
|
||||||
|
// This should be the easiest method of putting text anywhere.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Usage: label!("Foo: {}", bar)
|
/// Usage: label!("Foo: {}", bar)
|
||||||
|
@ -61,18 +80,25 @@ macro_rules! label {
|
||||||
|
|
||||||
impl Widget for Label {
|
impl Widget for Label {
|
||||||
fn ui(self, region: &mut Region) -> GuiResponse {
|
fn ui(self, region: &mut Region) -> GuiResponse {
|
||||||
let font = ®ion.fonts()[self.text_style];
|
let (text, text_size) = self.layout(region.cursor(), region);
|
||||||
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);
|
let interact = region.reserve_space(text_size, None);
|
||||||
region.add_text(interact.rect.min, self.text_style, text, self.text_color);
|
region.add_text(interact.rect.min, self.text_style, text, self.text_color);
|
||||||
region.response(interact)
|
region.response(interact)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<Label> for &str {
|
||||||
|
fn into(self) -> Label {
|
||||||
|
Label::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Label> for String {
|
||||||
|
fn into(self) -> Label {
|
||||||
|
Label::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
pub struct Hyperlink {
|
pub struct Hyperlink {
|
||||||
|
@ -322,7 +348,7 @@ impl Widget for RadioButton {
|
||||||
|
|
||||||
pub struct Separator {
|
pub struct Separator {
|
||||||
line_width: f32,
|
line_width: f32,
|
||||||
min_length: f32,
|
min_spacing: f32,
|
||||||
extra: f32,
|
extra: f32,
|
||||||
color: Color,
|
color: Color,
|
||||||
}
|
}
|
||||||
|
@ -331,7 +357,7 @@ impl Separator {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
line_width: 2.0,
|
line_width: 2.0,
|
||||||
min_length: 6.0,
|
min_spacing: 6.0,
|
||||||
extra: 0.0,
|
extra: 0.0,
|
||||||
color: color::WHITE,
|
color: color::WHITE,
|
||||||
}
|
}
|
||||||
|
@ -342,8 +368,8 @@ impl Separator {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn min_length(mut self, min_length: f32) -> Self {
|
pub fn min_spacing(mut self, min_spacing: f32) -> Self {
|
||||||
self.min_length = min_length;
|
self.min_spacing = min_spacing;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,7 +391,8 @@ impl Widget for Separator {
|
||||||
let extra = self.extra;
|
let extra = self.extra;
|
||||||
let (points, interact) = match region.direction() {
|
let (points, interact) = match region.direction() {
|
||||||
Direction::Horizontal => {
|
Direction::Horizontal => {
|
||||||
let interact = region.reserve_space(vec2(self.min_length, available_space.y), None);
|
let interact =
|
||||||
|
region.reserve_space(vec2(self.min_spacing, available_space.y), None);
|
||||||
(
|
(
|
||||||
vec![
|
vec![
|
||||||
pos2(interact.rect.center().x, interact.rect.top() - extra),
|
pos2(interact.rect.center().x, interact.rect.top() - extra),
|
||||||
|
@ -375,7 +402,8 @@ impl Widget for Separator {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Direction::Vertical => {
|
Direction::Vertical => {
|
||||||
let interact = region.reserve_space(vec2(available_space.x, self.min_length), None);
|
let interact =
|
||||||
|
region.reserve_space(vec2(available_space.x, self.min_spacing), None);
|
||||||
(
|
(
|
||||||
vec![
|
vec![
|
||||||
pos2(interact.rect.left() - extra, interact.rect.center().y),
|
pos2(interact.rect.left() - extra, interact.rect.center().y),
|
||||||
|
|
|
@ -127,7 +127,7 @@ impl<'a> Widget for Slider<'a> {
|
||||||
let slider_response = columns[0].add(slider_sans_text);
|
let slider_response = columns[0].add(slider_sans_text);
|
||||||
|
|
||||||
// Place the text in line with the slider on the left:
|
// Place the text in line with the slider on the left:
|
||||||
columns[1].set_height(slider_response.rect.height());
|
columns[1].set_desired_height(slider_response.rect.height());
|
||||||
columns[1].horizontal(|region| {
|
columns[1].horizontal(|region| {
|
||||||
region.set_align(Align::Center);
|
region.set_align(Align::Center);
|
||||||
region.add(Label::new(full_text).multiline(false));
|
region.add(Label::new(full_text).multiline(false));
|
||||||
|
|
Loading…
Reference in a new issue