Drawing refactor from Kludgine

This commit is contained in:
Jonathan Johnson 2023-11-17 08:07:37 -08:00
parent 01d45a836f
commit 8ae315e229
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
19 changed files with 229 additions and 240 deletions

6
Cargo.lock generated
View file

@ -588,9 +588,9 @@ dependencies = [
[[package]]
name = "etagere"
version = "0.2.8"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf22f748754352918e082e0039335ee92454a5d62bcaf69b5e8daf5907d9644"
checksum = "306960881d6c46bd0dd6b7f07442a441418c08d0d3e63d8d080b0f64c6343e4e"
dependencies = [
"euclid",
"svg_fmt",
@ -1089,7 +1089,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "kludgine"
version = "0.1.0"
source = "git+https://github.com/khonsulabs/kludgine#220df4ed07fcf86459a34591a7923d7ddb25e67e"
source = "git+https://github.com/khonsulabs/kludgine#74db2e6be9c79f384eafbe57af8fa92de8b8d03c"
dependencies = [
"ahash",
"alot",

View file

@ -4,7 +4,7 @@ use kludgine::figures::units::Px;
use kludgine::figures::{Angle, IntoSigned, Point, Rect, Size};
use kludgine::shapes::Shape;
use kludgine::text::{Text, TextOrigin};
use kludgine::Color;
use kludgine::{Color, DrawableExt};
fn main() -> gooey::Result<()> {
let mut angle = Angle::degrees(0);
@ -14,19 +14,16 @@ fn main() -> gooey::Result<()> {
let center = Point::from(context.gfx.size()).into_signed() / 2;
context.gfx.draw_text(
Text::new("Canvas exposes the full power of Kludgine", Color::WHITE)
.origin(TextOrigin::Center),
center - Point::new(Px(0), Px(100)),
None,
None,
.origin(TextOrigin::Center)
.translate_by(center - Point::new(Px(0), Px(100))),
);
context.gfx.draw_shape(
&Shape::filled_rect(
Shape::filled_rect(
Rect::new(Point::new(Px(-50), Px(-50)), Size::new(Px(100), Px(100))),
Color::RED,
),
center,
Some(angle),
None,
)
.translate_by(center)
.rotate_by(angle),
)
})
.tick(Tick::redraws_per_second(60))

View file

@ -15,16 +15,18 @@ fn main() -> gooey::Result {
(&username, &password).map_each(|(username, password)| validate(username, password));
// TODO this should be a grid layout to ensure proper visual alignment.
let username_row = "Username"
.and(username.clone().into_input().expand())
.into_columns();
let username_field = "Username"
.align_left()
.and(username.clone().into_input())
.into_rows();
let password_row = "Password"
let password_field = "Password"
.align_left()
.and(
// TODO secure input
password.clone().into_input().expand(),
password.clone().into_input(),
)
.into_columns();
.into_rows();
let buttons = "Cancel"
.into_button()
@ -46,13 +48,14 @@ fn main() -> gooey::Result {
)
.into_columns();
username_row
username_field
.pad()
.and(password_row.pad())
.and(password_field.pad())
.and(buttons.pad())
.into_rows()
.contain()
.width(Lp::points(300)..Lp::points(600))
.width(Lp::inches(3)..Lp::inches(6))
.pad()
.scroll()
.centered()
.expand()

View file

@ -8,6 +8,7 @@ use gooey::kludgine::Color;
use gooey::value::Dynamic;
use gooey::widgets::TileMap;
use gooey::{Run, Tick};
use kludgine::DrawableExt;
const PLAYER_SIZE: Px = Px(16);
@ -83,16 +84,14 @@ impl Object for Player {
fn render(&self, center: Point<Px>, zoom: f32, context: &mut Renderer<'_, '_>) {
let zoomed_size = PLAYER_SIZE * zoom;
context.draw_shape(
&Shape::filled_rect(
Shape::filled_rect(
Rect::new(
Point::new(-zoomed_size / 2, -zoomed_size / 2),
Size::squared(zoomed_size),
),
self.color,
),
center,
None,
None,
)
.translate_by(center),
)
}
}

60
examples/todo.rs Normal file
View file

@ -0,0 +1,60 @@
use gooey::value::Dynamic;
use gooey::widget::{Children, MakeWidget, WidgetInstance};
use gooey::widgets::input::InputValue;
use gooey::widgets::{Button, Label, Scroll, Stack};
use gooey::Run;
fn main() -> gooey::Result {
let tasks = Dynamic::default();
let children = Dynamic::default();
tasks.for_each(children.with_clone(|children| {
move |tasks: &Vec<Task>| {
update_task_widgets(tasks, &mut children.lock());
}
}));
let task_text = Dynamic::default();
let valid = task_text.map_each(|text: &String| !text.is_empty());
let task_form = Stack::columns(
task_text.clone().into_input().expand().and(
Button::new("Add Task")
.on_click(move |_| {
tasks.lock().push(Task::new(task_text.take()));
})
.into_default()
.with_enabled(valid),
),
);
let tasks = Scroll::vertical(Stack::rows(children));
Stack::rows(task_form.and(tasks)).expand().run()
}
struct Task {
widget: WidgetInstance,
}
impl Task {
pub fn new(text: String) -> Self {
let widget = Label::new(Dynamic::new(text)).align_left().make_widget();
Self { widget }
}
}
fn update_task_widgets(tasks: &[Task], children: &mut Children) {
'tasks: for (index, task) in tasks.iter().enumerate() {
for child in index..children.len() {
if children[child].id() == task.widget.id() {
if child != index {
children.swap(child, index);
}
continue 'tasks;
}
}
children.insert(index, task.widget.clone());
}
children.truncate(tasks.len());
}

View file

@ -527,8 +527,7 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
let visible_rect = Rect::from(self.gfx.region().size - (Px(1), Px(1)));
let focus_ring =
Shape::stroked_rect(visible_rect, color, options.into_px(self.gfx.scale()));
self.gfx
.draw_shape(&focus_ring, Point::default(), None, None);
self.gfx.draw_shape(&focus_ring);
}
/// Renders the default focus ring for this widget.

View file

@ -2,14 +2,13 @@ use std::ops::{Deref, DerefMut};
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{
self, Angle, Fraction, IntoSigned, IntoUnsigned, IsZero, Point, Rect, ScreenScale, ScreenUnit,
Size,
self, Fraction, IntoSigned, IntoUnsigned, IsZero, Point, Rect, ScreenScale, ScreenUnit, Size,
};
use kludgine::render::Renderer;
use kludgine::shapes::Shape;
use kludgine::text::{MeasuredText, Text, TextOrigin};
use kludgine::{
cosmic_text, ClipGuard, Color, Kludgine, ShaderScalable, ShapeSource, TextureSource,
cosmic_text, ClipGuard, Color, Drawable, Kludgine, ShaderScalable, ShapeSource, TextureSource,
};
/// A 2d graphics context
@ -141,28 +140,18 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
pub fn fill(&mut self, color: Color) {
if color.alpha() > 0 {
let rect = Rect::from(self.region.size);
self.draw_shape(
&Shape::filled_rect(rect, color),
Point::default(),
None,
None,
);
self.draw_shape(&Shape::filled_rect(rect, color));
}
}
/// Draws a shape at the origin, rotating and scaling as needed.
pub fn draw_shape<Unit>(
&mut self,
shape: &Shape<Unit, false>,
origin: Point<Unit>,
rotation_rads: Option<Angle>,
scale: Option<f32>,
) where
pub fn draw_shape<'a, Unit>(&mut self, shape: impl Into<Drawable<&'a Shape<Unit, false>, Unit>>)
where
Unit: IsZero + ShaderScalable + figures::ScreenUnit + Copy,
{
let translate = origin + Point::<Unit>::from_px(self.translation(), self.scale());
self.renderer
.draw_shape(shape, translate, rotation_rads, scale);
let mut shape = shape.into();
shape.translation += Point::<Unit>::from_px(self.translation(), self.scale());
self.renderer.draw_shape(shape);
}
/// Draws `texture` at `destination`, scaling as necessary.
@ -177,20 +166,18 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
/// Draws a shape that was created with texture coordinates, applying the
/// provided texture.
pub fn draw_textured_shape<Unit>(
pub fn draw_textured_shape<'shape, Unit, Shape>(
&mut self,
shape: &impl ShapeSource<Unit, true>,
shape: impl Into<Drawable<&'shape Shape, Unit>>,
texture: &impl TextureSource,
origin: Point<Unit>,
rotation: Option<Angle>,
scale: Option<f32>,
) where
Unit: IsZero + ShaderScalable + figures::ScreenUnit + Copy,
i32: From<<Unit as IntoSigned>::Signed>,
Shape: ShapeSource<Unit, true> + 'shape,
{
let translate = origin + Point::<Unit>::from_px(self.translation(), self.scale());
self.renderer
.draw_textured_shape(shape, texture, translate, rotation, scale);
let mut shape = shape.into();
shape.translation += Point::<Unit>::from_px(self.translation(), self.scale());
self.renderer.draw_textured_shape(shape, texture);
}
/// Measures `text` using the current text settings.
@ -204,17 +191,13 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
}
/// Draws `text` using the current text settings.
pub fn draw_text<'a, Unit>(
&mut self,
text: impl Into<Text<'a, Unit>>,
translate: Point<Unit>,
rotation: Option<Angle>,
scale: Option<f32>,
) where
pub fn draw_text<'a, Unit>(&mut self, text: impl Into<Drawable<Text<'a, Unit>, Unit>>)
where
Unit: ScreenUnit,
{
let translate = translate + Point::<Unit>::from_px(self.translation(), self.scale());
self.renderer.draw_text(text, translate, rotation, scale);
let mut text = text.into();
text.translation += Point::<Unit>::from_px(self.translation(), self.scale());
self.renderer.draw_text(text);
}
/// Prepares the text layout contained in `buffer` to be rendered.
@ -224,20 +207,18 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
///
/// `origin` allows controlling how the text will be drawn relative to the
/// coordinate provided in [`render()`](kludgine::PreparedGraphic::render).
pub fn draw_text_buffer<Unit>(
pub fn draw_text_buffer<'a, Unit>(
&mut self,
buffer: &cosmic_text::Buffer,
buffer: impl Into<Drawable<&'a cosmic_text::Buffer, Unit>>,
default_color: Color,
origin: TextOrigin<Px>,
translate: Point<Unit>,
rotation: Option<Angle>,
scale: Option<f32>,
) where
Unit: ScreenUnit,
{
let translate = translate + Point::<Unit>::from_px(self.translation(), self.scale());
let mut buffer = buffer.into();
buffer.translation += Point::<Unit>::from_px(self.translation(), self.scale());
self.renderer
.draw_text_buffer(buffer, default_color, origin, translate, rotation, scale);
.draw_text_buffer(buffer, default_color, origin);
}
/// Measures `buffer` and caches the results using `default_color` when
@ -260,19 +241,16 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
///
/// `origin` allows controlling how the text will be drawn relative to the
/// coordinate provided in [`render()`](kludgine::PreparedGraphic::render).
pub fn draw_measured_text<Unit>(
pub fn draw_measured_text<'a, Unit>(
&mut self,
text: &MeasuredText<Unit>,
text: impl Into<Drawable<&'a MeasuredText<Unit>, Unit>>,
origin: TextOrigin<Unit>,
translate: Point<Unit>,
rotation: Option<Angle>,
scale: Option<f32>,
) where
Unit: ScreenUnit,
{
let translate = translate + Point::<Unit>::from_px(self.translation(), self.scale());
self.renderer
.draw_measured_text(text, origin, translate, rotation, scale);
let mut text = text.into();
text.translation += Point::<Unit>::from_px(self.translation(), self.scale());
self.renderer.draw_measured_text(text, origin);
}
}

View file

@ -36,9 +36,9 @@ pub use self::tick::{InputState, Tick};
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ConstraintLimit {
/// The widget is expected to occupy a known size.
Known(UPx),
Fill(UPx),
/// The widget is expected to resize itself to fit within the size provided.
ClippedAfter(UPx),
SizeToFit(UPx),
}
impl ConstraintLimit {
@ -46,7 +46,7 @@ impl ConstraintLimit {
#[must_use]
pub fn max(self) -> UPx {
match self {
ConstraintLimit::Known(v) | ConstraintLimit::ClippedAfter(v) => v,
ConstraintLimit::Fill(v) | ConstraintLimit::SizeToFit(v) => v,
}
}
@ -62,8 +62,8 @@ impl ConstraintLimit {
{
let measured = measured.into_upx(scale);
match self {
ConstraintLimit::Known(size) => size.max(measured),
ConstraintLimit::ClippedAfter(_) => measured,
ConstraintLimit::Fill(size) => size.max(measured),
ConstraintLimit::SizeToFit(_) => measured,
}
}
}
@ -73,10 +73,8 @@ impl Sub<UPx> for ConstraintLimit {
fn sub(self, rhs: UPx) -> Self::Output {
match self {
ConstraintLimit::Known(px) => ConstraintLimit::Known(px.saturating_sub(rhs)),
ConstraintLimit::ClippedAfter(px) => {
ConstraintLimit::ClippedAfter(px.saturating_sub(rhs))
}
ConstraintLimit::Fill(px) => ConstraintLimit::Fill(px.saturating_sub(rhs)),
ConstraintLimit::SizeToFit(px) => ConstraintLimit::SizeToFit(px.saturating_sub(rhs)),
}
}
}

View file

@ -141,15 +141,15 @@ impl FrameInfo {
// into ClippedAfter mode to make the widget attempt to size the
// content to fit.
(Some(one), None) | (None, Some(one)) => {
ConstraintLimit::ClippedAfter(available.max() - one)
ConstraintLimit::SizeToFit(available.max() - one)
}
(None, None) => ConstraintLimit::ClippedAfter(available.max()),
(None, None) => ConstraintLimit::SizeToFit(available.max()),
}
}
fn measure(&self, available: ConstraintLimit, content: UPx) -> (UPx, UPx, UPx) {
match available {
ConstraintLimit::Known(size) => {
ConstraintLimit::Fill(size) => {
let remaining = size.saturating_sub(content);
let (a, b) = match (self.a, self.b) {
(Some(a), Some(b)) => (a, b),
@ -164,7 +164,7 @@ impl FrameInfo {
(a, b, size - a - b)
}
ConstraintLimit::ClippedAfter(_) => (
ConstraintLimit::SizeToFit(_) => (
self.a.unwrap_or_default(),
self.b.unwrap_or_default(),
content,

View file

@ -369,9 +369,7 @@ impl Widget for Button {
color,
two_lp_stroke,
);
context
.gfx
.draw_shape(&focus_ring, Point::default(), None, None);
context.gfx.draw_shape(&focus_ring);
} else if context.is_default() {
context.stroke_outline(context.get(&OutlineColor), two_lp_stroke);
} else {

View file

@ -178,12 +178,9 @@ impl WrapperWidget for CheckboxLabel {
match self.value.get_tracking_refresh(context) {
state @ (CheckboxState::Checked | CheckboxState::Indeterminant) => {
let color = context.get(&WidgetAccentColor);
context.gfx.draw_shape(
&Shape::filled_rect(checkbox_rect, color),
Point::default(),
None,
None,
);
context
.gfx
.draw_shape(&Shape::filled_rect(checkbox_rect, color));
let icon_area = checkbox_rect.inset(Lp::points(3).into_px(context.gfx.scale()));
let text_color = context.get(&TextColor);
let center = icon_area.origin + icon_area.size / 2;
@ -200,9 +197,6 @@ impl WrapperWidget for CheckboxLabel {
))
.build()
.stroke(text_color, stroke_options),
Point::default(),
None,
None,
);
} else {
context.gfx.draw_shape(
@ -213,20 +207,14 @@ impl WrapperWidget for CheckboxLabel {
))
.build()
.stroke(text_color, stroke_options),
Point::default(),
None,
None,
);
}
}
CheckboxState::Unchecked => {
let color = context.get(&OutlineColor);
context.gfx.draw_shape(
&Shape::stroked_rect(checkbox_rect, color, stroke_options),
Point::default(),
None,
None,
);
context
.gfx
.draw_shape(&Shape::stroked_rect(checkbox_rect, color, stroke_options));
}
}
}

View file

@ -103,8 +103,8 @@ impl WrapperWidget for Expand {
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> WrappedLayout {
let available_space = Size::new(
ConstraintLimit::Known(available_space.width.max()),
ConstraintLimit::Known(available_space.height.max()),
ConstraintLimit::Fill(available_space.width.max()),
ConstraintLimit::Fill(available_space.height.max()),
);
let child = self.child.mounted(&mut context.as_event_context());
let size = context.for_other(&child).layout(available_space);

View file

@ -19,7 +19,7 @@ use kludgine::figures::{
};
use kludgine::shapes::{Shape, StrokeOptions};
use kludgine::text::{MeasuredText, Text, TextOrigin};
use kludgine::Color;
use kludgine::{Color, DrawableExt};
use unicode_segmentation::UnicodeSegmentation;
use zeroize::Zeroizing;
@ -849,55 +849,47 @@ where
// Single line selection
let width = end_position.x - start_position.x;
context.gfx.draw_shape(
&Shape::filled_rect(
Shape::filled_rect(
Rect::new(start_position, Size::new(width, cache.measured.line_height)),
highlight,
),
padding,
None,
None,
)
.translate_by(padding),
);
} else {
// Draw from start to end of line,
let width = size.width.into_signed() - start_position.x;
context.gfx.draw_shape(
&Shape::filled_rect(
Shape::filled_rect(
Rect::new(start_position, Size::new(width, cache.measured.line_height)),
highlight,
),
padding,
None,
None,
)
.translate_by(padding),
);
// Fill region between
let bottom_of_first_line = start_position.y + cache.measured.line_height;
let distance_between = end_position.y - bottom_of_first_line;
if distance_between > 0 {
context.gfx.draw_shape(
&Shape::filled_rect(
Shape::filled_rect(
Rect::new(
Point::new(Px(0), bottom_of_first_line),
Size::new(size.width.into_signed(), distance_between),
),
highlight,
),
padding,
None,
None,
)
.translate_by(padding),
);
}
// Draw from 0 to end + width
context.gfx.draw_shape(
&Shape::filled_rect(
Shape::filled_rect(
Rect::new(
Point::new(Px(0), end_position.y),
Size::new(end_position.x + end_width, cache.measured.line_height),
),
highlight,
),
padding,
None,
None,
)
.translate_by(padding),
);
}
} else {
@ -906,16 +898,14 @@ where
if window_focused && cursor_state.visible {
let cursor_width = Lp::points(2).into_px(context.gfx.scale());
context.gfx.draw_shape(
&Shape::filled_rect(
Shape::filled_rect(
Rect::new(
Point::new(location.x - cursor_width / 2, location.y),
Size::new(cursor_width, cache.measured.line_height),
),
highlight, // TODO cursor should be a bold color, highlight probably not. This should have its own color.
),
padding,
None,
None,
highlight,
)
.translate_by(padding),
);
}
if window_focused {
@ -931,7 +921,7 @@ where
context
.gfx
.draw_measured_text(cache.measured, TextOrigin::TopLeft, padding, None, None);
.draw_measured_text(cache.measured.translate_by(padding), TextOrigin::TopLeft);
}
fn layout(

View file

@ -3,7 +3,7 @@
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{Point, ScreenScale, Size};
use kludgine::text::{MeasuredText, Text, TextOrigin};
use kludgine::Color;
use kludgine::{Color, DrawableExt};
use crate::context::{GraphicsContext, LayoutContext};
use crate::styles::components::{IntrinsicPadding, TextColor};
@ -71,7 +71,7 @@ impl Widget for Label {
context
.gfx
.draw_measured_text(prepared_text, TextOrigin::Center, center, None, None);
.draw_measured_text(prepared_text.translate_by(center), TextOrigin::Center);
}
fn layout(

View file

@ -124,10 +124,10 @@ fn override_constraint(
scale: Fraction,
) -> ConstraintLimit {
match constraint {
ConstraintLimit::Known(size) => ConstraintLimit::Known(range.clamp(size, scale)),
ConstraintLimit::ClippedAfter(clipped_after) => match (range.minimum(), range.maximum()) {
(Some(min), Some(max)) if min == max => ConstraintLimit::Known(min.into_upx(scale)),
_ => ConstraintLimit::ClippedAfter(range.clamp(clipped_after, scale)),
ConstraintLimit::Fill(size) => ConstraintLimit::Fill(range.clamp(size, scale)),
ConstraintLimit::SizeToFit(clipped_after) => match (range.minimum(), range.maximum()) {
(Some(min), Some(max)) if min == max => ConstraintLimit::Fill(min.into_upx(scale)),
_ => ConstraintLimit::SizeToFit(range.clamp(clipped_after, scale)),
},
}
}

View file

@ -129,33 +129,23 @@ impl Widget for Scroll {
let size = context.gfx.region().size;
if self.horizontal_bar.amount_hidden > 0 {
context.gfx.draw_shape(
&Shape::filled_rect(
Rect::new(
Point::new(self.horizontal_bar.offset, size.height - self.bar_width),
Size::new(self.horizontal_bar.size, self.bar_width),
),
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
context.gfx.draw_shape(&Shape::filled_rect(
Rect::new(
Point::new(self.horizontal_bar.offset, size.height - self.bar_width),
Size::new(self.horizontal_bar.size, self.bar_width),
),
Point::default(),
None,
None,
);
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
));
}
if self.vertical_bar.amount_hidden > 0 {
context.gfx.draw_shape(
&Shape::filled_rect(
Rect::new(
Point::new(size.width - self.bar_width, self.vertical_bar.offset),
Size::new(self.bar_width, self.vertical_bar.size),
),
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
context.gfx.draw_shape(&Shape::filled_rect(
Rect::new(
Point::new(size.width - self.bar_width, self.vertical_bar.offset),
Size::new(self.bar_width, self.vertical_bar.size),
),
Point::default(),
None,
None,
);
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
));
}
}
@ -175,12 +165,12 @@ impl Widget for Scroll {
Size::new(available_space.width.max(), available_space.height.max()).into_signed();
let max_extents = Size::new(
if self.enabled.x {
ConstraintLimit::ClippedAfter((control_size.width).into_unsigned())
ConstraintLimit::SizeToFit((control_size.width).into_unsigned())
} else {
available_space.width
},
if self.enabled.y {
ConstraintLimit::ClippedAfter((control_size.height).into_unsigned())
ConstraintLimit::SizeToFit((control_size.height).into_unsigned())
} else {
available_space.height
},
@ -285,8 +275,8 @@ impl Widget for Scroll {
fn constrain_child(constraint: ConstraintLimit, measured: Px) -> UPx {
let measured = measured.into_unsigned();
match constraint {
ConstraintLimit::Known(size) => size.min(measured),
ConstraintLimit::ClippedAfter(_) => measured,
ConstraintLimit::Fill(size) => size.min(measured),
ConstraintLimit::SizeToFit(_) => measured,
}
}

View file

@ -9,7 +9,7 @@ use kludgine::figures::{
Size,
};
use kludgine::shapes::Shape;
use kludgine::{Color, Origin};
use kludgine::{Color, DrawableExt, Origin};
use crate::animation::{LinearInterpolate, PercentBetween};
use crate::context::{EventContext, GraphicsContext, LayoutContext};
@ -85,53 +85,42 @@ impl<T> Slider<T> {
let half_track = spec.track_size / 2;
// Draw the track
if value_location > spec.half_knob {
context.gfx.draw_shape(
&Shape::filled_rect(
Rect::new(
flipped(
!self.horizontal,
Point::new(spec.half_knob, spec.half_knob - half_track),
),
flipped(!self.horizontal, Size::new(value_location, spec.track_size)),
context.gfx.draw_shape(&Shape::filled_rect(
Rect::new(
flipped(
!self.horizontal,
Point::new(spec.half_knob, spec.half_knob - half_track),
),
spec.track_color,
flipped(!self.horizontal, Size::new(value_location, spec.track_size)),
),
Point::default(),
None,
None,
);
spec.track_color,
));
}
if value_location < track_length {
context.gfx.draw_shape(
&Shape::filled_rect(
Rect::new(
flipped(
!self.horizontal,
Point::new(value_location, spec.half_knob - half_track),
),
flipped(
!self.horizontal,
Size::new(
track_length - value_location + spec.half_knob,
spec.track_size,
),
context.gfx.draw_shape(&Shape::filled_rect(
Rect::new(
flipped(
!self.horizontal,
Point::new(value_location, spec.half_knob - half_track),
),
flipped(
!self.horizontal,
Size::new(
track_length - value_location + spec.half_knob,
spec.track_size,
),
),
spec.inactive_track_color,
),
Point::default(),
None,
None,
);
spec.inactive_track_color,
));
}
// Draw the knob
context.gfx.draw_shape(
&Shape::filled_circle(spec.half_knob, spec.knob_color, Origin::Center),
flipped(!self.horizontal, Point::new(value_location, spec.half_knob)),
None,
None,
Shape::filled_circle(spec.half_knob, spec.knob_color, Origin::Center).translate_by(
flipped(!self.horizontal, Point::new(value_location, spec.half_knob)),
),
);
}
}
@ -229,7 +218,7 @@ where
.into_upx(context.gfx.scale());
match (available_space.width, available_space.height) {
(ConstraintLimit::Known(width), ConstraintLimit::Known(height)) => {
(ConstraintLimit::Fill(width), ConstraintLimit::Fill(height)) => {
// This comparison is done such that if width == height, we end
// up with a horizontal slider.
if width < height {
@ -240,13 +229,13 @@ where
Size::new(width.max(minimum_size), self.knob_size)
}
}
(ConstraintLimit::Known(width), ConstraintLimit::ClippedAfter(_)) => {
(ConstraintLimit::Fill(width), ConstraintLimit::SizeToFit(_)) => {
Size::new(width.max(minimum_size), self.knob_size)
}
(ConstraintLimit::ClippedAfter(_), ConstraintLimit::Known(height)) => {
(ConstraintLimit::SizeToFit(_), ConstraintLimit::Fill(height)) => {
Size::new(self.knob_size, height.max(minimum_size))
}
(ConstraintLimit::ClippedAfter(width), ConstraintLimit::ClippedAfter(_)) => {
(ConstraintLimit::SizeToFit(width), ConstraintLimit::SizeToFit(_)) => {
// When we have no limit on our, we still want to be draggable.
// Since we have no limit in both directions, we have to make a
// choice: horizontal or vertical. It seems to @ecton at the

View file

@ -409,7 +409,7 @@ impl Layout {
// widgets an opportunity to lay themselves out in the full area. This
// requires one extra layout call, so we avoid persisting layouts during
// the first loop if this is the case.
let needs_final_layout = !matches!(other_constraint, ConstraintLimit::Known(_));
let needs_final_layout = !matches!(other_constraint, ConstraintLimit::Fill(_));
// Measure the children that fit their content
self.other = UPx(0);
@ -418,7 +418,7 @@ impl Layout {
let (measured, other) = self.orientation.split_size(measure(
index,
self.orientation
.make_size(ConstraintLimit::ClippedAfter(remaining), other_constraint),
.make_size(ConstraintLimit::SizeToFit(remaining), other_constraint),
!needs_final_layout,
));
self.layouts[index].size = measured;
@ -432,7 +432,7 @@ impl Layout {
let (_, other) = self.orientation.split_size(measure(
index,
self.orientation.make_size(
ConstraintLimit::Known(self.layouts[index].size),
ConstraintLimit::Fill(self.layouts[index].size),
other_constraint,
),
!needs_final_layout,
@ -469,7 +469,7 @@ impl Layout {
let (_, measured) = self.orientation.split_size(measure(
index,
self.orientation.make_size(
ConstraintLimit::Known(self.layouts[index].size.into_upx(scale)),
ConstraintLimit::Fill(self.layouts[index].size.into_upx(scale)),
other_constraint,
),
!needs_final_layout,
@ -479,8 +479,8 @@ impl Layout {
}
self.other = match other_constraint {
ConstraintLimit::Known(max) => self.other.max(max),
ConstraintLimit::ClippedAfter(clip_limit) => self.other.min(clip_limit),
ConstraintLimit::Fill(max) => self.other.max(max),
ConstraintLimit::SizeToFit(clip_limit) => self.other.min(clip_limit),
};
// Finally, compute the offsets of all of the widgets.
@ -492,8 +492,8 @@ impl Layout {
self.orientation.split_size(measure(
index,
self.orientation.make_size(
ConstraintLimit::Known(self.layouts[index].size.into_upx(scale)),
ConstraintLimit::Known(self.other),
ConstraintLimit::Fill(self.layouts[index].size.into_upx(scale)),
ConstraintLimit::Fill(self.other),
),
true,
));
@ -635,8 +635,8 @@ mod tests {
fn size_to_fit() {
assert_measured_children(
&[Child::new(3, 1), Child::new(3, 1), Child::new(3, 1)],
ConstraintLimit::ClippedAfter(UPx(10)),
ConstraintLimit::ClippedAfter(UPx(10)),
ConstraintLimit::SizeToFit(UPx(10)),
ConstraintLimit::SizeToFit(UPx(10)),
&[UPx(3), UPx(3), UPx(3)],
UPx(9),
UPx(1),
@ -660,8 +660,8 @@ mod tests {
Child::new(3, 1).weighted(1),
Child::new(3, 1).weighted(1),
],
ConstraintLimit::Known(UPx(10)),
ConstraintLimit::ClippedAfter(UPx(10)),
ConstraintLimit::Fill(UPx(10)),
ConstraintLimit::SizeToFit(UPx(10)),
&[UPx(4), UPx(3), UPx(3)],
UPx(10),
UPx(7), // 20 / 3 = 6.666, rounded up is 7
@ -674,8 +674,8 @@ mod tests {
Child::new(3, 1).weighted(1),
Child::new(3, 1).weighted(1),
],
ConstraintLimit::Known(UPx(11)),
ConstraintLimit::ClippedAfter(UPx(11)),
ConstraintLimit::Fill(UPx(11)),
ConstraintLimit::SizeToFit(UPx(11)),
&[UPx(5), UPx(3), UPx(3)],
UPx(11),
UPx(7), // 20 / 3 = 6.666, rounded up is 7
@ -687,8 +687,8 @@ mod tests {
Child::new(3, 1).weighted(1),
Child::new(3, 1).weighted(1),
],
ConstraintLimit::Known(UPx(12)),
ConstraintLimit::ClippedAfter(UPx(12)),
ConstraintLimit::Fill(UPx(12)),
ConstraintLimit::SizeToFit(UPx(12)),
&[UPx(6), UPx(3), UPx(3)],
UPx(12),
UPx(4), // 20 / 6 = 3.666, rounded up is 4
@ -701,8 +701,8 @@ mod tests {
Child::new(3, 1).weighted(1),
Child::new(3, 1).weighted(1),
],
ConstraintLimit::Known(UPx(13)),
ConstraintLimit::ClippedAfter(UPx(13)),
ConstraintLimit::Fill(UPx(13)),
ConstraintLimit::SizeToFit(UPx(13)),
&[UPx(6), UPx(3), UPx(4)],
UPx(13),
UPx(4), // 20 / 6 = 3.666, rounded up is 4
@ -717,8 +717,8 @@ mod tests {
Child::new(3, 1).weighted(1),
Child::new(3, 1).weighted(1),
],
ConstraintLimit::Known(UPx(15)),
ConstraintLimit::ClippedAfter(UPx(15)),
ConstraintLimit::Fill(UPx(15)),
ConstraintLimit::SizeToFit(UPx(15)),
&[UPx(7), UPx(4), UPx(4)],
UPx(15),
UPx(1),

View file

@ -567,13 +567,13 @@ where
let actual_size = layout_context.layout(if is_expanded {
Size::new(
ConstraintLimit::Known(window_size.width),
ConstraintLimit::Known(window_size.height),
ConstraintLimit::Fill(window_size.width),
ConstraintLimit::Fill(window_size.height),
)
} else {
Size::new(
ConstraintLimit::ClippedAfter(window_size.width),
ConstraintLimit::ClippedAfter(window_size.height),
ConstraintLimit::SizeToFit(window_size.width),
ConstraintLimit::SizeToFit(window_size.height),
)
});
let render_size = actual_size.min(window_size);