`Px` and `PxVec2` reach the last places a pixel was a float: the window, the box a widget reads, the box it is compared against, and `PixelRegion`. A pointer, a wheel notch and a shaped glyph advance still arrive as floats, and each is put on the grid where it arrives. `Holds` is an interval of `Px`. `HOLDS_EPSILON_PX` is gone with the `exact`/tolerant split it existed for: `at` is the length a widget read, an open end is the next step along, and `same_px` is equality. `Span`'s margin from `5ed9e87` goes too -- the box a parent hands back and the sum of what its children asked for are counts of the same step, so the boundary decides the same way from either side. Three things had to be true for that, and were not: `Holds::through` inverts `px + rel * box`, which rounds -- so a part of a given length came from a range of boxes, and inverting the length alone gave a point that need not contain the box the part was drawn in. It now maps the half step either side, and one more for a length composed down the chain against the same length measured against the window. `RegionRemap` translates when a box only moved, rather than dividing to find each part's fraction and multiplying to place it again. Two roundings landed a step from where growing the tree that way does; a move is exact on a grid, which is the whole reason `tests/drift.rs` was written. A pixel is `1/1024` rather than `1/64`. At `1/64` the residue of a length reached two ways was one step, and one step was 0.016 px -- enough to move a box. `PX_SHIFT` and `REL_SHIFT` are the only statement of the grid now, and the shader's copy is prepended from them rather than written twice. Checked: fmt, clippy, 102 tests, 100 generated seeds in 75 s, all five shrinker cases at 300 seeds, and `tabs`, `view`, `minimal`, `text` and `random` byte-identical at 1920x1200. What the fuzzers ask for is now a step, not a twentieth of a pixel: the shrinker's five cases agree within one (`resize` exactly), and the oracle's two-operation cases within two. The residue is a single rounding either way -- it scales with the grid rather than accumulating, which is why it is a thousandth of a pixel now. Closing it means one way of asking how long a box is, rather than a chain composed down and a length measured against the window; that is a bigger change than this one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
142 lines
3.8 KiB
Rust
142 lines
3.8 KiB
Rust
mod build;
|
|
mod edit;
|
|
|
|
pub use build::*;
|
|
pub use edit::*;
|
|
use iris_core::util::MutDetect;
|
|
|
|
use crate::prelude::*;
|
|
use std::ops::{Deref, DerefMut};
|
|
|
|
pub struct Text {
|
|
pub content: MutDetect<String>,
|
|
view: TextView,
|
|
}
|
|
|
|
pub struct TextView {
|
|
pub attrs: TextAttrs,
|
|
pub buf: TextBuffer,
|
|
pub hint: Option<StrongWidget>,
|
|
}
|
|
|
|
impl TextView {
|
|
fn is_empty(&self) -> bool {
|
|
self.buf.is_empty()
|
|
}
|
|
|
|
pub fn wrap_width(&self) -> Option<f32> {
|
|
self.buf.wrap_width()
|
|
}
|
|
}
|
|
|
|
impl TextView {
|
|
pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<StrongWidget>) -> Self {
|
|
Self { attrs, buf, hint }
|
|
}
|
|
|
|
/// region where the text should be draw
|
|
/// does not include extra height or width from weird unicode
|
|
pub fn region(&self) -> UiRegion {
|
|
self.tex()
|
|
.map(|t| t.size)
|
|
.unwrap_or(Vec2::ZERO)
|
|
.align(self.align)
|
|
}
|
|
|
|
/// The text shaped for the width it is drawn in. The buffer keeps its
|
|
/// answers under the attrs too, so changing those asks a new question
|
|
/// rather than invalidating anything.
|
|
fn render(&mut self, painter: &mut Painter) -> &RenderedText {
|
|
let width = self.attrs.wrap.then(|| painter.px_len(Axis::X));
|
|
// The shaper measures in floats, which is where a glyph advance comes
|
|
// from; what it answers goes back on the grid.
|
|
let text = painter.render_text(&mut self.buf, &self.attrs, width.map(Px::to_f32));
|
|
// A greedy break is the same break at every width from its longest
|
|
// line up to the one it was made at: each line still fits, and none
|
|
// could take a word that did not fit in the wider box. A line too
|
|
// long to fit at all says nothing about narrower boxes.
|
|
if let Some(width) = width {
|
|
painter.holds(Axis::X, Px::from_f32(text.size.x).min(width)..=width);
|
|
}
|
|
text
|
|
}
|
|
|
|
pub fn tex(&self) -> Option<&RenderedText> {
|
|
self.buf.rendered()
|
|
}
|
|
/// Draws the text, and says where the glyphs went and what they use.
|
|
pub fn draw(&mut self, painter: &mut Painter) -> (UiRegion, Size) {
|
|
let align = self.align;
|
|
if self.is_empty() && self.hint.is_some() {
|
|
let region = self.render(painter).size.align(align);
|
|
let size = match &self.hint {
|
|
Some(hint) => painter.widget(hint).size(),
|
|
None => Size::ZERO,
|
|
};
|
|
return (region, size);
|
|
}
|
|
|
|
let tex = self.render(painter);
|
|
let region = tex.size.align(align);
|
|
let size = Size::px(tex.size);
|
|
let within = region.within(&painter.region());
|
|
painter.glyphs(tex, within);
|
|
(region, size)
|
|
}
|
|
|
|
pub fn content(&self) -> String {
|
|
self.buf.text().to_string()
|
|
}
|
|
}
|
|
|
|
impl Text {
|
|
pub fn new(content: impl Into<String>) -> Self {
|
|
let content: String = content.into();
|
|
Self {
|
|
view: TextView::new(TextBuffer::new(&content), TextAttrs::default(), None),
|
|
content: content.into(),
|
|
}
|
|
}
|
|
fn update_buf(&mut self) {
|
|
if self.content.changed {
|
|
self.content.changed = false;
|
|
self.view.buf.set_text(self.content.as_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Widget for Text {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
self.update_buf();
|
|
self.view.draw(painter).1
|
|
}
|
|
}
|
|
|
|
impl Deref for Text {
|
|
type Target = TextAttrs;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.view
|
|
}
|
|
}
|
|
|
|
impl DerefMut for Text {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.view
|
|
}
|
|
}
|
|
|
|
impl Deref for TextView {
|
|
type Target = TextAttrs;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.attrs
|
|
}
|
|
}
|
|
|
|
impl DerefMut for TextView {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.attrs
|
|
}
|
|
}
|