[refactor] extract paint code from Ui/Context to new struct Painter

This commit is contained in:
Emil Ernerfeldt 2020-07-30 14:11:09 +02:00
parent 5c14a15f8c
commit 11df21e39e
18 changed files with 333 additions and 309 deletions

View file

@ -87,7 +87,7 @@ impl State {
*p = rect.center() + v; *p = rect.center() + v;
} }
ui.add_paint_cmd(PaintCmd::Path { ui.painter().add(PaintCmd::Path {
path: Path::from_point_loop(&points), path: Path::from_point_loop(&points),
closed: true, closed: true,
fill: None, fill: None,
@ -219,7 +219,7 @@ impl CollapsingHeader {
state.toggle(ui); state.toggle(ui);
} }
let where_to_put_background = ui.paint_list_len(); let bg_index = ui.painter().add(PaintCmd::Noop);
{ {
let (mut icon_rect, _) = ui.style().icon_rectangles(interact.rect); let (mut icon_rect, _) = ui.style().icon_rectangles(interact.rect);
@ -234,15 +234,16 @@ impl CollapsingHeader {
state.paint_icon(ui, &icon_interact); state.paint_icon(ui, &icon_interact);
} }
ui.add_galley( let painter = ui.painter();
painter.add_galley(
text_pos, text_pos,
galley, galley,
label.text_style, label.text_style,
Some(ui.style().interact(&interact).stroke_color), ui.style().interact(&interact).stroke_color,
); );
ui.insert_paint_cmd( painter.set(
where_to_put_background, bg_index,
PaintCmd::Rect { PaintCmd::Rect {
corner_radius: ui.style().interact(&interact).corner_radius, corner_radius: ui.style().interact(&interact).corner_radius,
fill: ui.style().interact(&interact).bg_fill, fill: ui.style().interact(&interact).bg_fill,

View file

@ -1,6 +1,6 @@
//! Frame container //! Frame container
use crate::{paint::*, *}; use crate::{layers::PaintCmdIdx, paint::*, *};
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Frame { pub struct Frame {
@ -62,7 +62,7 @@ impl Frame {
pub struct Prepared { pub struct Prepared {
pub frame: Frame, pub frame: Frame,
outer_rect_bounds: Rect, outer_rect_bounds: Rect,
where_to_put_background: usize, where_to_put_background: PaintCmdIdx,
pub content_ui: Ui, pub content_ui: Ui,
} }
@ -70,7 +70,7 @@ impl Frame {
pub fn begin(self, ui: &mut Ui) -> Prepared { pub fn begin(self, ui: &mut Ui) -> Prepared {
let outer_rect_bounds = ui.available(); let outer_rect_bounds = ui.available();
let inner_rect = outer_rect_bounds.shrink2(self.margin); let inner_rect = outer_rect_bounds.shrink2(self.margin);
let where_to_put_background = ui.paint_list_len(); let where_to_put_background = ui.painter().add(PaintCmd::Noop);
let content_ui = ui.child_ui(inner_rect); let content_ui = ui.child_ui(inner_rect);
Prepared { Prepared {
frame: self, frame: self,
@ -105,7 +105,7 @@ impl Prepared {
.. ..
} = self; } = self;
ui.insert_paint_cmd( ui.painter().set(
where_to_put_background, where_to_put_background,
PaintCmd::Rect { PaintCmd::Rect {
corner_radius: frame.corner_radius, corner_radius: frame.corner_radius,

View file

@ -226,7 +226,7 @@ impl Resize {
// so we must follow the contents: // so we must follow the contents:
state.desired_size = state.desired_size.max(state.last_content_size); state.desired_size = state.desired_size.max(state.last_content_size);
state.desired_size = ui.round_vec_to_pixels(state.desired_size); state.desired_size = ui.painter().round_vec_to_pixels(state.desired_size);
// We are as large as we look // We are as large as we look
ui.allocate_space(state.desired_size); ui.allocate_space(state.desired_size);
@ -240,7 +240,7 @@ impl Resize {
if self.outline && corner_interact.is_some() { if self.outline && corner_interact.is_some() {
let rect = Rect::from_min_size(content_ui.top_left(), state.desired_size); let rect = Rect::from_min_size(content_ui.top_left(), state.desired_size);
let rect = rect.expand(2.0); // breathing room for content let rect = rect.expand(2.0); // breathing room for content
ui.add_paint_cmd(paint::PaintCmd::Rect { ui.painter().add(paint::PaintCmd::Rect {
rect, rect,
corner_radius: 3.0, corner_radius: 3.0,
fill: None, fill: None,
@ -259,12 +259,12 @@ impl Resize {
ui.memory().resize.insert(id, state); ui.memory().resize.insert(id, state);
if ui.ctx().style().debug_resize { if ui.ctx().style().debug_resize {
ui.ctx().debug_rect( ui.ctx().debug_painter().debug_rect(
Rect::from_min_size(content_ui.top_left(), state.desired_size), Rect::from_min_size(content_ui.top_left(), state.desired_size),
color::GREEN, color::GREEN,
"desired_size", "desired_size",
); );
ui.ctx().debug_rect( ui.ctx().debug_painter().debug_rect(
Rect::from_min_size(content_ui.top_left(), state.last_content_size), Rect::from_min_size(content_ui.top_left(), state.last_content_size),
color::LIGHT_BLUE, color::LIGHT_BLUE,
"last_content_size", "last_content_size",
@ -277,11 +277,13 @@ fn paint_resize_corner(ui: &mut Ui, interact: &InteractInfo) {
let color = ui.style().interact(interact).stroke_color; let color = ui.style().interact(interact).stroke_color;
let width = ui.style().interact(interact).stroke_width; let width = ui.style().interact(interact).stroke_width;
let corner = ui.round_pos_to_pixels(interact.rect.right_bottom()); let painter = ui.painter();
let corner = painter.round_pos_to_pixels(interact.rect.right_bottom());
let mut w = 2.0; let mut w = 2.0;
while w < 12.0 { while w < 12.0 {
ui.add_paint_cmd(paint::PaintCmd::line_segment( painter.add(paint::PaintCmd::line_segment(
[pos2(corner.x - w, corner.y), pos2(corner.x, corner.y - w)], [pos2(corner.x - w, corner.y), pos2(corner.x, corner.y - w)],
color, color,
width, width,

View file

@ -268,14 +268,14 @@ impl Prepared {
let handle_fill = style.interact(&interact).fill; let handle_fill = style.interact(&interact).fill;
let handle_outline = style.interact(&interact).rect_outline; let handle_outline = style.interact(&interact).rect_outline;
ui.add_paint_cmd(paint::PaintCmd::Rect { ui.painter().add(paint::PaintCmd::Rect {
rect: outer_scroll_rect, rect: outer_scroll_rect,
corner_radius, corner_radius,
fill: Some(ui.style().dark_bg_color), fill: Some(ui.style().dark_bg_color),
outline: None, outline: None,
}); });
ui.add_paint_cmd(paint::PaintCmd::Rect { ui.painter().add(paint::PaintCmd::Rect {
rect: handle_rect.expand(-2.0), rect: handle_rect.expand(-2.0),
corner_radius, corner_radius,
fill: Some(handle_fill), fill: Some(handle_fill),

View file

@ -511,7 +511,7 @@ fn paint_frame_interaction(
path.add_circle_quadrant(pos2(max.x - cr, min.y + cr), cr, 3.0); path.add_circle_quadrant(pos2(max.x - cr, min.y + cr), cr, 3.0);
path.add_line_segment([pos2(max.x, min.y + cr), pos2(max.x, max.y - cr)]); path.add_line_segment([pos2(max.x, min.y + cr), pos2(max.x, max.y - cr)]);
} }
ui.add_paint_cmd(PaintCmd::Path { ui.painter().add(PaintCmd::Path {
path, path,
closed: false, closed: false,
fill: None, fill: None,
@ -614,7 +614,7 @@ impl TitleBar {
let left = outer_rect.left(); let left = outer_rect.left();
let right = outer_rect.right(); let right = outer_rect.right();
let y = content_rect.top() + ui.style().item_spacing.y * 0.5; let y = content_rect.top() + ui.style().item_spacing.y * 0.5;
ui.add_paint_cmd(PaintCmd::LineSegment { ui.painter().add(PaintCmd::LineSegment {
points: [pos2(left, y), pos2(right, y)], points: [pos2(left, y), pos2(right, y)],
style: ui.style().interact.inactive.rect_outline.unwrap(), style: ui.style().interact.inactive.rect_outline.unwrap(),
}); });
@ -650,12 +650,12 @@ fn close_button(ui: &mut Ui, rect: Rect) -> InteractInfo {
let stroke_color = ui.style().interact(&interact).stroke_color; let stroke_color = ui.style().interact(&interact).stroke_color;
let stroke_width = ui.style().interact(&interact).stroke_width; let stroke_width = ui.style().interact(&interact).stroke_width;
ui.add_paint_cmd(PaintCmd::line_segment( ui.painter().add(PaintCmd::line_segment(
[rect.left_top(), rect.right_bottom()], [rect.left_top(), rect.right_bottom()],
stroke_color, stroke_color,
stroke_width, stroke_width,
)); ));
ui.add_paint_cmd(PaintCmd::line_segment( ui.painter().add(PaintCmd::line_segment(
[rect.right_top(), rect.left_bottom()], [rect.right_top(), rect.left_bottom()],
stroke_color, stroke_color,
stroke_width, stroke_width,

View file

@ -2,7 +2,7 @@ use std::sync::Arc;
use {ahash::AHashMap, parking_lot::Mutex}; use {ahash::AHashMap, parking_lot::Mutex};
use crate::{layout::align_rect, paint::*, *}; use crate::{paint::*, *};
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
struct PaintStats { struct PaintStats {
@ -234,7 +234,7 @@ impl Context {
/// Generate a id from the given source. /// Generate a id from the given source.
/// If it is not unique, an error will be printed at the given position. /// If it is not unique, an error will be printed at the given position.
pub fn make_unique_id<IdSource>(&self, source: IdSource, pos: Pos2) -> Id pub fn make_unique_id<IdSource>(self: &Arc<Self>, source: IdSource, pos: Pos2) -> Id
where where
IdSource: std::hash::Hash + std::fmt::Debug + Copy, IdSource: std::hash::Hash + std::fmt::Debug + Copy,
{ {
@ -246,19 +246,25 @@ impl Context {
} }
/// If the given Id is not unique, an error will be printed at the given position. /// If the given Id is not unique, an error will be printed at the given position.
pub fn register_unique_id(&self, id: Id, source_name: impl std::fmt::Debug, pos: Pos2) -> Id { pub fn register_unique_id(
self: &Arc<Self>,
id: Id,
source_name: impl std::fmt::Debug,
pos: Pos2,
) -> Id {
if let Some(clash_pos) = self.used_ids.lock().insert(id, pos) { if let Some(clash_pos) = self.used_ids.lock().insert(id, pos) {
let painter = self.debug_painter();
if clash_pos.distance(pos) < 4.0 { if clash_pos.distance(pos) < 4.0 {
self.show_error( painter.error(
pos, pos,
&format!("use of non-unique ID {:?} (name clash?)", source_name), &format!("use of non-unique ID {:?} (name clash?)", source_name),
); );
} else { } else {
self.show_error( painter.error(
clash_pos, clash_pos,
&format!("first use of non-unique ID {:?} (name clash?)", source_name), &format!("first use of non-unique ID {:?} (name clash?)", source_name),
); );
self.show_error( painter.error(
pos, pos,
&format!( &format!(
"second use of non-unique ID {:?} (name clash?)", "second use of non-unique ID {:?} (name clash?)",
@ -422,103 +428,12 @@ impl Context {
} }
} }
} }
}
// --------------------------------------------------------------------- /// ## Painting
impl Context {
pub fn show_error(&self, pos: Pos2, text: impl Into<String>) { pub fn debug_painter(self: &Arc<Self>) -> Painter {
let text = text.into(); Painter::new(self.clone(), Layer::debug(), self.rect())
let align = (Align::Min, Align::Min);
let layer = Layer::debug();
let text_style = TextStyle::Monospace;
let font = &self.fonts()[text_style];
let galley = font.layout_multiline(text, f32::INFINITY);
let rect = align_rect(Rect::from_min_size(pos, galley.size), align);
self.add_paint_cmd(
layer,
PaintCmd::Rect {
corner_radius: 0.0,
fill: Some(color::gray(0, 240)),
outline: Some(LineStyle::new(1.0, color::RED)),
rect: rect.expand(2.0),
},
);
self.add_galley(layer, rect.min, galley, text_style, Some(color::RED));
}
pub fn debug_text(&self, pos: Pos2, text: impl Into<String>) {
let text = text.into();
let layer = Layer::debug();
let align = (Align::Min, Align::Min);
self.floating_text(
layer,
pos,
text,
TextStyle::Monospace,
align,
Some(color::YELLOW),
);
}
pub fn debug_rect(&self, rect: Rect, color: Color, name: impl Into<String>) {
let text = format!("{} {:?}", name.into(), rect);
let layer = Layer::debug();
self.add_paint_cmd(
layer,
PaintCmd::Rect {
corner_radius: 0.0,
fill: None,
outline: Some(LineStyle::new(2.0, color)),
rect,
},
);
let align = (Align::Min, Align::Min);
let text_style = TextStyle::Monospace;
self.floating_text(layer, rect.min, text, text_style, align, Some(color));
}
/// Show some text anywhere on screen.
/// To center the text at the given position, use `align: (Center, Center)`.
pub fn floating_text(
&self,
layer: Layer,
pos: Pos2,
text: String,
text_style: TextStyle,
align: (Align, Align),
text_color: Option<Color>,
) -> Rect {
let font = &self.fonts()[text_style];
let galley = font.layout_multiline(text, f32::INFINITY);
let rect = align_rect(Rect::from_min_size(pos, galley.size), align);
self.add_galley(layer, rect.min, galley, text_style, text_color);
rect
}
/// Already layed out text.
pub fn add_galley(
&self,
layer: Layer,
pos: Pos2,
galley: font::Galley,
text_style: TextStyle,
color: Option<Color>,
) {
let color = color.unwrap_or_else(|| self.style().text_color);
self.add_paint_cmd(
layer,
PaintCmd::Text {
pos,
galley,
text_style,
color,
},
);
}
pub fn add_paint_cmd(&self, layer: Layer, paint_cmd: PaintCmd) {
self.graphics()
.layer(layer)
.push((Rect::everything(), paint_cmd))
} }
} }

View file

@ -469,7 +469,7 @@ impl BoxPainting {
outline: Some(LineStyle::new(self.stroke_width, gray(255, 255))), outline: Some(LineStyle::new(self.stroke_width, gray(255, 255))),
}); });
} }
ui.add_paint_cmds(cmds); ui.painter().extend(cmds);
} }
} }
@ -498,7 +498,8 @@ impl Painting {
let rect = ui.allocate_space(ui.available_finite().size()); let rect = ui.allocate_space(ui.available_finite().size());
let interact = ui.interact(rect, ui.id(), Sense::drag()); let interact = ui.interact(rect, ui.id(), Sense::drag());
let rect = interact.rect; let rect = interact.rect;
ui.set_clip_rect(ui.clip_rect().intersect(rect)); // Make sure we don't paint out of bounds let clip_rect = ui.clip_rect().intersect(rect); // Make sure we don't paint out of bounds
let painter = Painter::new(ui.ctx().clone(), ui.layer(), clip_rect);
if self.lines.is_empty() { if self.lines.is_empty() {
self.lines.push(vec![]); self.lines.push(vec![]);
@ -520,7 +521,7 @@ impl Painting {
for line in &self.lines { for line in &self.lines {
if line.len() >= 2 { if line.len() >= 2 {
let points: Vec<Pos2> = line.iter().map(|p| rect.min + *p).collect(); let points: Vec<Pos2> = line.iter().map(|p| rect.min + *p).collect();
ui.add_paint_cmd(PaintCmd::Path { painter.add(PaintCmd::Path {
path: Path::from_open_points(&points), path: Path::from_open_points(&points),
closed: false, closed: false,
outline: Some(LineStyle::new(2.0, LIGHT_GRAY)), outline: Some(LineStyle::new(2.0, LIGHT_GRAY)),

View file

@ -50,18 +50,18 @@ impl FractalClock {
ui.ctx().request_repaint(); ui.ctx().request_repaint();
} }
self.fractal_ui(ui, ui.available_finite()); let painter = Painter::new(ui.ctx().clone(), ui.layer(), ui.available_finite());
self.fractal_ui(&painter);
let frame = Frame::popup(ui.style()) Frame::popup(ui.style())
.fill(Some(color::gray(34, 160))) .fill(Some(color::gray(34, 160)))
.outline(None); .outline(None)
.show(&mut ui.left_column(320.0), |ui| {
frame.show(&mut ui.left_column(320.0), |ui| { CollapsingHeader::new("Settings").show(ui, |ui| self.options_ui(ui));
CollapsingHeader::new("Settings").show(ui, |ui| self.options_ui(ui)); });
});
// Make sure we allocate what we used (everything) // Make sure we allocate what we used (everything)
ui.allocate_space(ui.available_finite().size()); ui.allocate_space(painter.clip_rect().size());
} }
fn options_ui(&mut self, ui: &mut Ui) { fn options_ui(&mut self, ui: &mut Ui) {
@ -96,7 +96,9 @@ impl FractalClock {
); );
} }
fn fractal_ui(&mut self, ui: &mut Ui, rect: Rect) { fn fractal_ui(&mut self, painter: &Painter) {
let rect = painter.clip_rect();
struct Hand { struct Hand {
length: f32, length: f32,
angle: f32, angle: f32,
@ -126,13 +128,13 @@ impl FractalClock {
]; ];
let scale = self.zoom * rect.width().min(rect.height()); let scale = self.zoom * rect.width().min(rect.height());
let mut paint_line = |points: [Pos2; 2], color: Color, width: f32| { let paint_line = |points: [Pos2; 2], color: Color, width: f32| {
let line = [ let line = [
rect.center() + scale * points[0].to_vec2(), rect.center() + scale * points[0].to_vec2(),
rect.center() + scale * points[1].to_vec2(), rect.center() + scale * points[1].to_vec2(),
]; ];
ui.add_paint_cmd(PaintCmd::line_segment([line[0], line[1]], color, width)); painter.add(PaintCmd::line_segment([line[0], line[1]], color, width));
}; };
let hand_rotations = [ let hand_rotations = [

View file

@ -30,7 +30,7 @@ impl Texture {
}; };
let mut triangles = Triangles::default(); let mut triangles = Triangles::default();
triangles.add_rect(top_left, bottom_right); triangles.add_rect(top_left, bottom_right);
ui.add_paint_cmd(PaintCmd::Triangles(triangles)); ui.painter().add(PaintCmd::Triangles(triangles));
if ui.hovered(rect) { if ui.hovered(rect) {
show_tooltip(ui.ctx(), |ui| { show_tooltip(ui.ctx(), |ui| {
@ -55,7 +55,7 @@ impl Texture {
}; };
let mut triangles = Triangles::default(); let mut triangles = Triangles::default();
triangles.add_rect(top_left, bottom_right); triangles.add_rect(top_left, bottom_right);
ui.add_paint_cmd(PaintCmd::Triangles(triangles)); ui.painter().add(PaintCmd::Triangles(triangles));
}); });
} }
} }

View file

@ -16,7 +16,7 @@ pub enum Order {
Debug, Debug,
} }
/// An ideintifer for a paint layer. /// An identifier for a paint layer.
/// Also acts as an identifier for `Area`:s. /// Also acts as an identifier for `Area`:s.
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "with_serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "with_serde", derive(serde::Deserialize, serde::Serialize))]
@ -34,15 +34,38 @@ impl Layer {
} }
} }
#[derive(Clone, Copy, PartialEq)]
pub struct PaintCmdIdx(usize);
/// Each `PaintCmd` is paired with a clip rectangle. /// Each `PaintCmd` is paired with a clip rectangle.
type PaintList = Vec<(Rect, PaintCmd)>; #[derive(Clone, Default)]
pub struct PaintList(Vec<(Rect, PaintCmd)>);
impl PaintList {
/// Returns the index of the new command that can be used with `PaintList::set`.
pub fn add(&mut self, clip_rect: Rect, cmd: PaintCmd) -> PaintCmdIdx {
let idx = PaintCmdIdx(self.0.len());
self.0.push((clip_rect, cmd));
idx
}
pub fn extend(&mut self, clip_rect: Rect, mut cmds: Vec<PaintCmd>) {
self.0.extend(cmds.drain(..).map(|cmd| (clip_rect, cmd)))
}
/// Modify an existing command.
pub fn set(&mut self, idx: PaintCmdIdx, clip_rect: Rect, cmd: PaintCmd) {
assert!(idx.0 < self.0.len());
self.0[idx.0] = (clip_rect, cmd);
}
}
// TODO: improve this // TODO: improve this
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct GraphicLayers(AHashMap<Layer, PaintList>); pub struct GraphicLayers(AHashMap<Layer, PaintList>);
impl GraphicLayers { impl GraphicLayers {
pub fn layer(&mut self, layer: Layer) -> &mut PaintList { pub fn list(&mut self, layer: Layer) -> &mut PaintList {
self.0.entry(layer).or_default() self.0.entry(layer).or_default()
} }
@ -54,12 +77,12 @@ impl GraphicLayers {
for layer in area_order { for layer in area_order {
if let Some(commands) = self.0.get_mut(layer) { if let Some(commands) = self.0.get_mut(layer) {
all_commands.extend(commands.drain(..)); all_commands.extend(commands.0.drain(..));
} }
} }
if let Some(commands) = self.0.get_mut(&Layer::debug()) { if let Some(commands) = self.0.get_mut(&Layer::debug()) {
all_commands.extend(commands.drain(..)); all_commands.extend(commands.0.drain(..));
} }
all_commands.into_iter() all_commands.into_iter()

View file

@ -35,6 +35,7 @@ pub mod math;
mod memory; mod memory;
mod movement_tracker; mod movement_tracker;
pub mod paint; pub mod paint;
mod painter;
mod style; mod style;
mod types; mod types;
mod ui; mod ui;
@ -52,6 +53,7 @@ pub use {
memory::Memory, memory::Memory,
movement_tracker::MovementTracker, movement_tracker::MovementTracker,
paint::{color, Color, PaintJobs, TextStyle, Texture}, paint::{color, Color, PaintJobs, TextStyle, Texture},
painter::Painter,
style::Style, style::Style,
types::*, types::*,
ui::Ui, ui::Ui,

View file

@ -6,6 +6,8 @@ use {
// TODO: rename, e.g. `paint::Cmd`? // TODO: rename, e.g. `paint::Cmd`?
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum PaintCmd { pub enum PaintCmd {
/// Paint nothing. This can be useful as a placeholder.
Noop,
Circle { Circle {
center: Pos2, center: Pos2,
fill: Option<Color>, fill: Option<Color>,

View file

@ -564,6 +564,7 @@ pub fn tessellate_paint_command(
path.clear(); path.clear();
match command { match command {
PaintCmd::Noop => {}
PaintCmd::Circle { PaintCmd::Circle {
center, center,
fill, fill,

165
egui/src/painter.rs Normal file
View file

@ -0,0 +1,165 @@
use std::sync::Arc;
use crate::{
align_rect, color,
layers::PaintCmdIdx,
math::{Pos2, Rect, Vec2},
paint::{font, Fonts, LineStyle, PaintCmd, TextStyle},
Align, Color, Context, Layer,
};
/// Helper to paint shapes and text to a specific region on a specific layer.
#[derive(Clone)]
pub struct Painter {
/// Source of fonts and destination of paint commands
ctx: Arc<Context>,
/// Where we paint
layer: Layer,
/// Everything painted in this `Painter` will be clipped against this.
/// This means nothing outside of this rectangle will be visible on screen.
clip_rect: Rect,
}
impl Painter {
pub fn new(ctx: Arc<Context>, layer: Layer, clip_rect: Rect) -> Self {
Self {
ctx,
layer,
clip_rect,
}
}
}
/// ## Accessors etc
impl Painter {
pub fn ctx(&self) -> &Arc<Context> {
&self.ctx
}
/// Available fonts
pub fn fonts(&self) -> &Fonts {
self.ctx.fonts()
}
/// Where we paint
pub fn layer(&self) -> Layer {
self.layer
}
/// Everything painted in this `Painter` will be clipped against this.
/// This means nothing outside of this rectangle will be visible on screen.
pub fn clip_rect(&self) -> Rect {
self.clip_rect
}
/// Everything painted in this `Painter` will be clipped against this.
/// This means nothing outside of this rectangle will be visible on screen.
pub fn set_clip_rect(&mut self, clip_rect: Rect) {
self.clip_rect = clip_rect;
}
pub fn round_to_pixel(&self, point: f32) -> f32 {
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)
}
}
/// ## Low level
impl Painter {
/// It is up to the caller to make sure there is room for this.
/// Can be used for free painting.
/// NOTE: all coordinates are screen coordinates!
pub fn add(&self, paint_cmd: PaintCmd) -> PaintCmdIdx {
self.ctx
.graphics()
.list(self.layer)
.add(self.clip_rect, paint_cmd)
}
pub fn extend(&self, cmds: Vec<PaintCmd>) {
self.ctx
.graphics()
.list(self.layer)
.extend(self.clip_rect, cmds);
}
/// Modify an existing command.
pub fn set(&self, idx: PaintCmdIdx, cmd: PaintCmd) {
self.ctx
.graphics()
.list(self.layer)
.set(idx, self.clip_rect, cmd)
}
}
/// ## Debug painting
impl Painter {
pub fn debug_rect(&mut self, rect: Rect, color: Color, text: impl Into<String>) {
self.add(PaintCmd::Rect {
corner_radius: 0.0,
fill: None,
outline: Some(LineStyle::new(1.0, color)),
rect,
});
let align = (Align::Min, Align::Min);
let text_style = TextStyle::Monospace;
self.floating_text(rect.min, text.into(), text_style, align, color);
}
pub fn error(&self, pos: Pos2, text: impl Into<String>) {
let text = text.into();
let align = (Align::Min, Align::Min);
let text_style = TextStyle::Monospace;
let font = &self.fonts()[text_style];
let galley = font.layout_multiline(text, f32::INFINITY);
let rect = align_rect(Rect::from_min_size(pos, galley.size), align);
self.add(PaintCmd::Rect {
corner_radius: 0.0,
fill: Some(color::gray(0, 240)),
outline: Some(LineStyle::new(1.0, color::RED)),
rect: rect.expand(2.0),
});
self.add_galley(rect.min, galley, text_style, color::RED);
}
}
/// ## Text
impl Painter {
/// Show some text anywhere in the ui.
/// To center the text at the given position, use `align: (Center, Center)`.
/// If you want to draw text floating on top of everything,
/// consider using `Context.floating_text` instead.
pub fn floating_text(
&self,
pos: Pos2,
text: impl Into<String>,
text_style: TextStyle,
align: (Align, Align),
text_color: Color,
) -> Rect {
let font = &self.fonts()[text_style];
let galley = font.layout_multiline(text.into(), f32::INFINITY);
let rect = align_rect(Rect::from_min_size(pos, galley.size), align);
self.add_galley(rect.min, galley, text_style, text_color);
rect
}
/// Already layed out text.
pub fn add_galley(&self, pos: Pos2, galley: font::Galley, text_style: TextStyle, color: Color) {
self.add(PaintCmd::Text {
pos,
galley,
text_style,
color,
});
}
}

View file

@ -5,9 +5,6 @@ use crate::{color::*, containers::*, layout::*, paint::*, widgets::*, *};
/// Represents a region of the screen /// Represents a region of the screen
/// with a type of layout (horizontal or vertical). /// with a type of layout (horizontal or vertical).
pub struct Ui { pub struct Ui {
/// How we access input, output and memory
ctx: Arc<Context>,
/// ID of this ui. /// ID of this ui.
/// Generated based on id of parent ui together with /// Generated based on id of parent ui together with
/// another source of child identity (e.g. window title). /// another source of child identity (e.g. window title).
@ -15,12 +12,7 @@ pub struct Ui {
/// Hopefully unique. /// Hopefully unique.
id: Id, id: Id,
/// Where to put the graphics output of this Ui painter: Painter,
layer: Layer,
/// Everything painted in this ui will be clipped against this.
/// This means nothing outside of this rectangle will be visible on screen.
clip_rect: Rect,
/// The `rect` represents where in screen-space the ui is /// The `rect` represents where in screen-space the ui is
/// and its max size (original available_space). /// and its max size (original available_space).
@ -62,11 +54,10 @@ impl Ui {
pub fn new(ctx: Arc<Context>, layer: Layer, id: Id, rect: Rect) -> Self { pub fn new(ctx: Arc<Context>, layer: Layer, id: Id, rect: Rect) -> Self {
let style = ctx.style(); let style = ctx.style();
let clip_rect = rect.expand(style.clip_rect_margin);
Ui { Ui {
ctx,
id, id,
layer, painter: Painter::new(ctx, layer, clip_rect),
clip_rect: rect.expand(style.clip_rect_margin),
desired_rect: rect, desired_rect: rect,
child_bounds: Rect::from_min_size(rect.min, Vec2::zero()), // TODO: Rect::nothing() ? child_bounds: Rect::from_min_size(rect.min, Vec2::zero()), // TODO: Rect::nothing() ?
style, style,
@ -77,14 +68,11 @@ impl Ui {
} }
pub fn child_ui(&mut self, child_rect: Rect) -> Self { pub fn child_ui(&mut self, child_rect: Rect) -> Self {
let clip_rect = self.clip_rect(); // Keep it unless the child excplicitly desires differently
let id = self.make_position_id(); // TODO: is this a good idea? let id = self.make_position_id(); // TODO: is this a good idea?
self.child_count += 1; self.child_count += 1;
Ui { Ui {
ctx: self.ctx.clone(),
id, id,
layer: self.layer, painter: self.painter.clone(),
clip_rect,
desired_rect: child_rect, desired_rect: child_rect,
child_bounds: Rect::from_min_size(child_rect.min, Vec2::zero()), // TODO: Rect::nothing() ? child_bounds: Rect::from_min_size(child_rect.min, Vec2::zero()), // TODO: Rect::nothing() ?
style: self.style.clone(), style: self.style.clone(),
@ -96,18 +84,6 @@ impl Ui {
// ------------------------------------------------- // -------------------------------------------------
pub fn round_to_pixel(&self, point: f32) -> f32 {
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)
}
pub fn id(&self) -> Id { pub fn id(&self) -> Id {
self.id self.id
} }
@ -122,34 +98,45 @@ impl Ui {
} }
pub fn ctx(&self) -> &Arc<Context> { pub fn ctx(&self) -> &Arc<Context> {
&self.ctx self.painter.ctx()
}
/// Use this to paint stuff within this `Ui`.
pub fn painter(&self) -> &Painter {
&self.painter
}
/// Use this to paint stuff within this `Ui`.
pub fn layer(&self) -> Layer {
self.painter().layer()
} }
pub fn input(&self) -> &InputState { pub fn input(&self) -> &InputState {
self.ctx.input() self.ctx().input()
} }
pub fn memory(&self) -> parking_lot::MutexGuard<'_, Memory> { pub fn memory(&self) -> parking_lot::MutexGuard<'_, Memory> {
self.ctx.memory() self.ctx().memory()
} }
pub fn output(&self) -> parking_lot::MutexGuard<'_, Output> { pub fn output(&self) -> parking_lot::MutexGuard<'_, Output> {
self.ctx.output() self.ctx().output()
} }
pub fn fonts(&self) -> &Fonts { pub fn fonts(&self) -> &Fonts {
self.ctx.fonts() self.ctx().fonts()
} }
/// Screen-space rectangle for clipping what we paint in this ui. /// Screen-space rectangle for clipping what we paint in this ui.
/// This is used, for instance, to avoid painting outside a window that is smaller /// This is used, for instance, to avoid painting outside a window that is smaller than its contents.
/// than its contents.
pub fn clip_rect(&self) -> Rect { pub fn clip_rect(&self) -> Rect {
self.clip_rect self.painter.clip_rect()
} }
/// Screen-space rectangle for clipping what we paint in this ui.
/// This is used, for instance, to avoid painting outside a window that is smaller than its contents.
pub fn set_clip_rect(&mut self, clip_rect: Rect) { pub fn set_clip_rect(&mut self, clip_rect: Rect) {
self.clip_rect = clip_rect; self.painter.set_clip_rect(clip_rect);
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -269,7 +256,8 @@ impl Ui {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
pub fn contains_mouse(&self, rect: Rect) -> bool { pub fn contains_mouse(&self, rect: Rect) -> bool {
self.ctx.contains_mouse(self.layer, self.clip_rect, rect) self.ctx()
.contains_mouse(self.layer(), self.clip_rect(), rect)
} }
pub fn has_kb_focus(&self, id: Id) -> bool { pub fn has_kb_focus(&self, id: Id) -> bool {
@ -293,7 +281,7 @@ impl Ui {
{ {
let id = self.id.with(&id_source); let id = self.id.with(&id_source);
// TODO: clip name clash error messages to clip rect // 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)
} }
/// Ideally, all widgets should use this. TODO /// Ideally, all widgets should use this. TODO
@ -309,13 +297,13 @@ impl Ui {
self.id.with(&explicit_id_source) self.id.with(&explicit_id_source)
} else { } else {
let id = self.id.with(default_id_source); let id = self.id.with(default_id_source);
if self.ctx.is_unique_id(id) { if self.ctx().is_unique_id(id) {
id id
} else { } else {
self.make_position_id() self.make_position_id()
} }
}; };
self.ctx self.ctx()
.register_unique_id(id, default_id_source.unwrap_or_default(), self.cursor) .register_unique_id(id, default_id_source.unwrap_or_default(), self.cursor)
} }
@ -334,13 +322,13 @@ impl Ui {
/// # Interaction /// # Interaction
impl Ui { impl Ui {
pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> InteractInfo { pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> InteractInfo {
self.ctx self.ctx()
.interact(self.layer, self.clip_rect, rect, Some(id), sense) .interact(self.layer(), self.clip_rect(), rect, Some(id), sense)
} }
pub fn interact_hover(&self, rect: Rect) -> InteractInfo { pub fn interact_hover(&self, rect: Rect) -> InteractInfo {
self.ctx self.ctx()
.interact(self.layer, self.clip_rect, rect, None, Sense::nothing()) .interact(self.layer(), self.clip_rect(), rect, None, Sense::nothing())
} }
pub fn hovered(&self, rect: Rect) -> bool { pub fn hovered(&self, rect: Rect) -> bool {
@ -365,7 +353,7 @@ impl Ui {
double_clicked, double_clicked,
active, active,
rect, rect,
ctx: self.ctx.clone(), ctx: self.ctx().clone(),
} }
} }
@ -385,8 +373,8 @@ impl Ui {
/// ///
/// You may get LESS space than you asked for if the current layout won't fit what you asked for. /// You may get LESS space than you asked for if the current layout won't fit what you asked for.
pub fn allocate_space(&mut self, child_size: Vec2) -> Rect { pub fn allocate_space(&mut self, child_size: Vec2) -> Rect {
let child_size = self.round_vec_to_pixels(child_size); let child_size = self.painter().round_vec_to_pixels(child_size);
self.cursor = self.round_pos_to_pixels(self.cursor); self.cursor = self.painter().round_pos_to_pixels(self.cursor);
// For debug rendering // For debug rendering
let too_wide = child_size.x > self.available().width(); let too_wide = child_size.x > self.available().width();
@ -395,7 +383,7 @@ impl Ui {
let rect = self.reserve_space_impl(child_size); let rect = self.reserve_space_impl(child_size);
if self.style().debug_widget_rects { if self.style().debug_widget_rects {
self.add_paint_cmd(PaintCmd::Rect { self.painter.add(PaintCmd::Rect {
rect, rect,
corner_radius: 0.0, corner_radius: 0.0,
outline: Some(LineStyle::new(1.0, LIGHT_BLUE)), outline: Some(LineStyle::new(1.0, LIGHT_BLUE)),
@ -405,8 +393,10 @@ impl Ui {
let color = color::srgba(200, 0, 0, 255); let color = color::srgba(200, 0, 0, 255);
let width = 2.5; let width = 2.5;
let mut paint_line_seg = let paint_line_seg = |a, b| {
|a, b| self.add_paint_cmd(PaintCmd::line_segment([a, b], color, width)); self.painter
.add(PaintCmd::line_segment([a, b], color, width))
};
if too_wide { if too_wide {
paint_line_seg(rect.left_top(), rect.left_bottom()); paint_line_seg(rect.left_top(), rect.left_bottom());
@ -437,96 +427,6 @@ impl Ui {
} }
} }
/// # Painting related stuff
impl Ui {
/// It is up to the caller to make sure there is room for this.
/// Can be used for free painting.
/// NOTE: all coordinates are screen coordinates!
pub fn add_paint_cmd(&mut self, paint_cmd: PaintCmd) {
self.ctx
.graphics()
.layer(self.layer)
.push((self.clip_rect(), paint_cmd))
}
pub fn add_paint_cmds(&mut self, mut cmds: Vec<PaintCmd>) {
let clip_rect = self.clip_rect();
self.ctx
.graphics()
.layer(self.layer)
.extend(cmds.drain(..).map(|cmd| (clip_rect, cmd)));
}
/// Insert a paint cmd before existing ones
pub fn insert_paint_cmd(&mut self, pos: usize, paint_cmd: PaintCmd) {
self.ctx
.graphics()
.layer(self.layer)
.insert(pos, (self.clip_rect(), paint_cmd));
}
pub fn paint_list_len(&self) -> usize {
self.ctx.graphics().layer(self.layer).len()
}
/// Paint some debug text at current cursor
pub fn debug_text(&self, text: impl Into<String>) {
self.debug_text_at(self.cursor, text);
}
pub fn debug_text_at(&self, pos: Pos2, text: impl Into<String>) {
self.ctx.debug_text(pos, text);
}
pub fn debug_rect(&mut self, rect: Rect, text: impl Into<String>) {
self.add_paint_cmd(PaintCmd::Rect {
corner_radius: 0.0,
fill: None,
outline: Some(LineStyle::new(1.0, color::RED)),
rect,
});
let align = (Align::Min, Align::Min);
let text_style = TextStyle::Monospace;
self.floating_text(rect.min, text.into(), text_style, align, Some(color::RED));
}
/// Show some text anywhere in the ui.
/// To center the text at the given position, use `align: (Center, Center)`.
/// If you want to draw text floating on top of everything,
/// consider using `Context.floating_text` instead.
pub fn floating_text(
&mut self,
pos: Pos2,
text: impl Into<String>,
text_style: TextStyle,
align: (Align, Align),
text_color: Option<Color>,
) -> Rect {
let font = &self.fonts()[text_style];
let galley = font.layout_multiline(text.into(), f32::INFINITY);
let rect = align_rect(Rect::from_min_size(pos, galley.size), align);
self.add_galley(rect.min, galley, text_style, text_color);
rect
}
/// Already layed out text.
pub fn add_galley(
&mut self,
pos: Pos2,
galley: font::Galley,
text_style: TextStyle,
color: Option<Color>,
) {
let color = color.unwrap_or_else(|| self.style().text_color);
self.add_paint_cmd(PaintCmd::Text {
pos,
galley,
text_style,
color,
});
}
}
/// # Adding widgets /// # Adding widgets
impl Ui { impl Ui {
pub fn add(&mut self, widget: impl Widget) -> GuiResponse { pub fn add(&mut self, widget: impl Widget) -> GuiResponse {
@ -637,9 +537,9 @@ impl Ui {
// draw a grey line on the left to mark the indented section // draw a grey line on the left to mark the indented section
let line_start = child_rect.min - indent * 0.5; let line_start = child_rect.min - indent * 0.5;
let line_start = self.round_pos_to_pixels(line_start); let line_start = self.painter().round_pos_to_pixels(line_start);
let line_end = pos2(line_start.x, line_start.y + size.y - 2.0); let line_end = pos2(line_start.x, line_start.y + size.y - 2.0);
self.add_paint_cmd(PaintCmd::line_segment( self.painter.add(PaintCmd::line_segment(
[line_start, line_end], [line_start, line_end],
gray(150, 255), gray(150, 255),
self.style.line_width, self.style.line_width,

View file

@ -102,7 +102,9 @@ impl Label {
// This should be the easiest method of putting text anywhere. // This should be the easiest method of putting text anywhere.
pub fn paint_galley(&self, ui: &mut Ui, pos: Pos2, galley: font::Galley) { pub fn paint_galley(&self, ui: &mut Ui, pos: Pos2, galley: font::Galley) {
ui.add_galley(pos, galley, self.text_style, self.text_color); let text_color = self.text_color.unwrap_or_else(|| ui.style().text_color);
ui.painter()
.add_galley(pos, galley, self.text_style, text_color);
} }
} }
@ -188,10 +190,10 @@ impl Widget for Hyperlink {
for line in &galley.lines { for line in &galley.lines {
let pos = interact.rect.min; let pos = interact.rect.min;
let y = pos.y + line.y_max; let y = pos.y + line.y_max;
let y = ui.round_to_pixel(y); let y = ui.painter().round_to_pixel(y);
let min_x = pos.x + line.min_x(); let min_x = pos.x + line.min_x();
let max_x = pos.x + line.max_x(); let max_x = pos.x + line.max_x();
ui.add_paint_cmd(PaintCmd::line_segment( ui.painter().add(PaintCmd::line_segment(
[pos2(min_x, y), pos2(max_x, y)], [pos2(min_x, y), pos2(max_x, y)],
color, color,
ui.style().line_width, ui.style().line_width,
@ -199,7 +201,8 @@ impl Widget for Hyperlink {
} }
} }
ui.add_galley(interact.rect.min, galley, text_style, Some(color)); ui.painter()
.add_galley(interact.rect.min, galley, text_style, color);
interact interact
} }
@ -279,7 +282,7 @@ impl Widget for Button {
let interact = ui.interact(rect, id, sense); let interact = ui.interact(rect, id, sense);
let text_cursor = interact.rect.left_center() + vec2(padding.x, -0.5 * galley.size.y); let text_cursor = interact.rect.left_center() + vec2(padding.x, -0.5 * galley.size.y);
let bg_fill = fill.or(ui.style().interact(&interact).bg_fill); let bg_fill = fill.or(ui.style().interact(&interact).bg_fill);
ui.add_paint_cmd(PaintCmd::Rect { ui.painter().add(PaintCmd::Rect {
corner_radius: ui.style().interact(&interact).corner_radius, corner_radius: ui.style().interact(&interact).corner_radius,
fill: bg_fill, fill: bg_fill,
outline: ui.style().interact(&interact).rect_outline, outline: ui.style().interact(&interact).rect_outline,
@ -287,7 +290,8 @@ impl Widget for Button {
}); });
let stroke_color = ui.style().interact(&interact).stroke_color; let stroke_color = ui.style().interact(&interact).stroke_color;
let text_color = text_color.unwrap_or(stroke_color); let text_color = text_color.unwrap_or(stroke_color);
ui.add_galley(text_cursor, galley, text_style, Some(text_color)); ui.painter()
.add_galley(text_cursor, galley, text_style, text_color);
interact interact
} }
} }
@ -340,7 +344,7 @@ impl<'a> Widget for Checkbox<'a> {
*checked = !*checked; *checked = !*checked;
} }
let (small_icon_rect, big_icon_rect) = ui.style().icon_rectangles(interact.rect); let (small_icon_rect, big_icon_rect) = ui.style().icon_rectangles(interact.rect);
ui.add_paint_cmd(PaintCmd::Rect { ui.painter().add(PaintCmd::Rect {
corner_radius: ui.style().interact(&interact).corner_radius, corner_radius: ui.style().interact(&interact).corner_radius,
fill: ui.style().interact(&interact).bg_fill, fill: ui.style().interact(&interact).bg_fill,
outline: ui.style().interact(&interact).rect_outline, outline: ui.style().interact(&interact).rect_outline,
@ -350,7 +354,7 @@ impl<'a> Widget for Checkbox<'a> {
let stroke_color = ui.style().interact(&interact).stroke_color; let stroke_color = ui.style().interact(&interact).stroke_color;
if *checked { if *checked {
ui.add_paint_cmd(PaintCmd::Path { ui.painter().add(PaintCmd::Path {
path: Path::from_open_points(&[ path: Path::from_open_points(&[
pos2(small_icon_rect.left(), small_icon_rect.center().y), pos2(small_icon_rect.left(), small_icon_rect.center().y),
pos2(small_icon_rect.center().x, small_icon_rect.bottom()), pos2(small_icon_rect.center().x, small_icon_rect.bottom()),
@ -363,7 +367,8 @@ impl<'a> Widget for Checkbox<'a> {
} }
let text_color = text_color.unwrap_or(stroke_color); let text_color = text_color.unwrap_or(stroke_color);
ui.add_galley(text_cursor, galley, text_style, Some(text_color)); ui.painter()
.add_galley(text_cursor, galley, text_style, text_color);
interact interact
} }
} }
@ -417,7 +422,9 @@ impl Widget for RadioButton {
let (small_icon_rect, big_icon_rect) = ui.style().icon_rectangles(interact.rect); let (small_icon_rect, big_icon_rect) = ui.style().icon_rectangles(interact.rect);
ui.add_paint_cmd(PaintCmd::Circle { let painter = ui.painter();
painter.add(PaintCmd::Circle {
center: big_icon_rect.center(), center: big_icon_rect.center(),
fill: bg_fill, fill: bg_fill,
outline: ui.style().interact(&interact).rect_outline, // TODO outline: ui.style().interact(&interact).rect_outline, // TODO
@ -425,7 +432,7 @@ impl Widget for RadioButton {
}); });
if checked { if checked {
ui.add_paint_cmd(PaintCmd::Circle { painter.add(PaintCmd::Circle {
center: small_icon_rect.center(), center: small_icon_rect.center(),
fill: Some(stroke_color), fill: Some(stroke_color),
outline: None, outline: None,
@ -434,7 +441,7 @@ impl Widget for RadioButton {
} }
let text_color = text_color.unwrap_or(stroke_color); let text_color = text_color.unwrap_or(stroke_color);
ui.add_galley(text_cursor, galley, text_style, Some(text_color)); painter.add_galley(text_cursor, galley, text_style, text_color);
interact interact
} }
} }
@ -520,7 +527,7 @@ impl Widget for Separator {
) )
} }
}; };
ui.add_paint_cmd(PaintCmd::LineSegment { ui.painter().add(PaintCmd::LineSegment {
points, points,
style: LineStyle::new(line_width, color), style: LineStyle::new(line_width, color),
}); });

View file

@ -107,7 +107,7 @@ impl<'a> Widget for Slider<'a> {
} }
let text_on_top = self.text_on_top.unwrap_or_default(); let text_on_top = self.text_on_top.unwrap_or_default();
let text_color = self.text_color; let text_color = self.text_color.unwrap_or_else(|| ui.style().text_color);
let value = (self.get_set_value)(None); let value = (self.get_set_value)(None);
let full_text = format!("{}: {:.*}", text, self.precision, value); let full_text = format!("{}: {:.*}", text, self.precision, value);
@ -116,7 +116,7 @@ impl<'a> Widget for Slider<'a> {
if text_on_top { if text_on_top {
let galley = font.layout_single_line(full_text); let galley = font.layout_single_line(full_text);
let pos = ui.allocate_space(galley.size).min; let pos = ui.allocate_space(galley.size).min;
ui.add_galley(pos, galley, text_style, text_color); ui.painter().add_galley(pos, galley, text_style, text_color);
slider_sans_text.ui(ui) slider_sans_text.ui(ui)
} else { } else {
ui.columns(2, |columns| { ui.columns(2, |columns| {
@ -162,21 +162,21 @@ impl<'a> Widget for Slider<'a> {
let value = self.get_value_f32(); let value = self.get_value_f32();
let rect = interact.rect; let rect = interact.rect;
let rail_radius = ui.round_to_pixel((height / 8.0).max(2.0)); let rail_radius = ui.painter().round_to_pixel((height / 8.0).max(2.0));
let rail_rect = Rect::from_min_max( let rail_rect = Rect::from_min_max(
pos2(interact.rect.left(), rect.center().y - rail_radius), pos2(interact.rect.left(), rect.center().y - rail_radius),
pos2(interact.rect.right(), rect.center().y + rail_radius), pos2(interact.rect.right(), rect.center().y + rail_radius),
); );
let marker_center_x = remap_clamp(value, range, left..=right); let marker_center_x = remap_clamp(value, range, left..=right);
ui.add_paint_cmd(PaintCmd::Rect { ui.painter().add(PaintCmd::Rect {
rect: rail_rect, rect: rail_rect,
corner_radius: rail_radius, corner_radius: rail_radius,
fill: Some(ui.style().background_fill), fill: Some(ui.style().background_fill),
outline: Some(LineStyle::new(1.0, color::gray(200, 255))), // TODO outline: Some(LineStyle::new(1.0, color::gray(200, 255))), // TODO
}); });
ui.add_paint_cmd(PaintCmd::Circle { ui.painter().add(PaintCmd::Circle {
center: pos2(marker_center_x, rail_rect.center().y), center: pos2(marker_center_x, rail_rect.center().y),
radius: handle_radius, radius: handle_radius,
fill: Some(ui.style().interact(&interact).fill), fill: Some(ui.style().interact(&interact).fill),

View file

@ -129,9 +129,11 @@ impl<'t> Widget for TextEdit<'t> {
// dbg!(&galley); // dbg!(&galley);
} }
let painter = ui.painter();
{ {
let bg_rect = interact.rect.expand(2.0); // breathing room for content let bg_rect = interact.rect.expand(2.0); // breathing room for content
ui.add_paint_cmd(PaintCmd::Rect { painter.add(PaintCmd::Rect {
rect: bg_rect, rect: bg_rect,
corner_radius: ui.style().interact.style(&interact).corner_radius, corner_radius: ui.style().interact.style(&interact).corner_radius,
fill: Some(ui.style().dark_bg_color), fill: Some(ui.style().dark_bg_color),
@ -146,7 +148,7 @@ impl<'t> Widget for TextEdit<'t> {
if show_cursor { if show_cursor {
if let Some(cursor) = state.cursor { if let Some(cursor) = state.cursor {
let cursor_pos = interact.rect.min + galley.char_start_pos(cursor); let cursor_pos = interact.rect.min + galley.char_start_pos(cursor);
ui.add_paint_cmd(PaintCmd::line_segment( painter.add(PaintCmd::line_segment(
[cursor_pos, cursor_pos + vec2(0.0, line_spacing)], [cursor_pos, cursor_pos + vec2(0.0, line_spacing)],
color::WHITE, color::WHITE,
ui.style().text_cursor_width, ui.style().text_cursor_width,
@ -156,7 +158,8 @@ impl<'t> Widget for TextEdit<'t> {
ui.ctx().request_repaint(); // TODO: only when cursor blinks on or off ui.ctx().request_repaint(); // TODO: only when cursor blinks on or off
} }
ui.add_galley(interact.rect.min, galley, text_style, text_color); let text_color = text_color.unwrap_or_else(|| ui.style().text_color);
painter.add_galley(interact.rect.min, galley, text_style, text_color);
ui.memory().text_edit.insert(id, state); ui.memory().text_edit.insert(id, state);
interact interact
} }