Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
983e15cd9a | ||
|
|
338f10f070 | ||
|
|
234abb8052 | ||
|
|
80b28fff1a | ||
|
|
2026e5d088 | ||
|
|
15a156595b | ||
|
|
dc008441e4 | ||
|
|
5fe7c3b6df | ||
|
|
71e46c125a | ||
|
|
2d15195a58 | ||
|
|
f8b7912181 |
No files matched your search
Generated
+1
@@ -1189,6 +1189,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"fxhash",
|
||||
"image",
|
||||
"log",
|
||||
"parley",
|
||||
"swash",
|
||||
"wgpu",
|
||||
|
||||
@@ -35,6 +35,7 @@ image = "0.25.6"
|
||||
parley = "0.11.1"
|
||||
swash = "0.2.10"
|
||||
fxhash = "0.2.1"
|
||||
log = "0.4.29"
|
||||
arboard = "3.6.1"
|
||||
iris-core = { path = "core" }
|
||||
iris-macro = { path = "macro" }
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
images
|
||||
settings (sampler)
|
||||
|
||||
text
|
||||
figure out ways to speed up / what costs the most
|
||||
resizing (per frame) is really slow (assuming painter isn't griefing)
|
||||
j is weird / fix x offset
|
||||
|
||||
masks r just made to bare minimum work
|
||||
|
||||
scaling
|
||||
could be just a simple scaling factor that multiplies abs
|
||||
and need to ensure text uses raw abs and not scaled abs
|
||||
naming? (pt, px)
|
||||
want to keep (drawn) regions using px? or should I add another field to UiScalar/Vec
|
||||
field could be best solution so redrawing stuff isn't needed & you can specify both as user
|
||||
consider typed TextureHandle<T> variants for distinct texture uses
|
||||
|
||||
WidgetRef<W> or smth instead of Id
|
||||
enum that's either an Id or an actual concrete instance of W
|
||||
@@ -24,17 +11,6 @@ WidgetRef<W> or smth instead of Id
|
||||
maybe introduce InnerWidget trait to allow for editors to expose & modify inner type
|
||||
maybe could also store a parent widget and keep using InnerWidget trait? unsure if possible
|
||||
|
||||
really weird limitation:
|
||||
I don't think you can currently remove an element from a parent and put it in a child of the same parent
|
||||
because it removes the unused children after the entire parent redraw
|
||||
but the child gets drawn during that, so it will think the child is still active !!!
|
||||
or something like that idk, maybe I need a special enum for parent that includes a undecided state where it may or may not get redrawn by the parent
|
||||
or just do ref counting and ensure all drawn things == 1 afterwards (seems like best way)
|
||||
ok so I'm removing the limit for now
|
||||
|
||||
don't forget I'm streaming
|
||||
|
||||
tags
|
||||
vecs for each widget type?
|
||||
|
||||
POTENTIAL BUG: closures that store IDs will not decrement the id!!! need to not increment id if moved into closure somehow??? wait no, need to decrement ID every time an event fn is added...... only if the id is used in it..??
|
||||
@@ -10,3 +10,4 @@ image = { workspace = true }
|
||||
parley = { workspace = true }
|
||||
swash = { workspace = true }
|
||||
fxhash = { workspace = true }
|
||||
log = { workspace = true }
|
||||
+95
-54
@@ -1,8 +1,12 @@
|
||||
use crate::{Align, GlyphAtlas, GlyphKey, PlacedGlyph, RegionAlign, Textures, UiColor, util::Vec2};
|
||||
use crate::{
|
||||
Align, GlyphAtlas, GlyphEntry, GlyphKey, PlacedGlyph, RegionAlign, Textures, UiColor,
|
||||
util::Vec2,
|
||||
};
|
||||
use parley::{
|
||||
Alignment, AlignmentOptions, FontContext, FontFamily, FontFamilyName, GenericFamily, Layout,
|
||||
LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty,
|
||||
};
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use swash::{
|
||||
FontRef,
|
||||
scale::{Render, ScaleContext, Source, StrikeWith},
|
||||
@@ -10,18 +14,18 @@ use swash::{
|
||||
};
|
||||
|
||||
pub struct TextData {
|
||||
pub font_cx: FontContext,
|
||||
pub layout_cx: LayoutContext<UiColor>,
|
||||
scale_cx: ScaleContext,
|
||||
pub font_ctx: FontContext,
|
||||
pub layout_ctx: LayoutContext<UiColor>,
|
||||
scale_ctx: ScaleContext,
|
||||
pub atlas: GlyphAtlas,
|
||||
}
|
||||
|
||||
impl Default for TextData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
font_cx: FontContext::new(),
|
||||
layout_cx: LayoutContext::new(),
|
||||
scale_cx: ScaleContext::new(),
|
||||
font_ctx: FontContext::new(),
|
||||
layout_ctx: LayoutContext::new(),
|
||||
scale_ctx: ScaleContext::new(),
|
||||
atlas: GlyphAtlas::default(),
|
||||
}
|
||||
}
|
||||
@@ -77,7 +81,13 @@ impl Default for TextAttrs {
|
||||
pub struct TextBuffer {
|
||||
text: String,
|
||||
layout: Layout<UiColor>,
|
||||
shaped: Option<(TextAttrs, Option<f32>)>,
|
||||
layout_key: Option<LayoutKey>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct LayoutKey {
|
||||
attrs: TextAttrs,
|
||||
max_width: Option<f32>,
|
||||
}
|
||||
|
||||
impl TextBuffer {
|
||||
@@ -85,7 +95,7 @@ impl TextBuffer {
|
||||
Self {
|
||||
text: text.into(),
|
||||
layout: Layout::new(),
|
||||
shaped: None,
|
||||
layout_key: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,13 +119,13 @@ impl TextBuffer {
|
||||
let text = text.into();
|
||||
if text != self.text {
|
||||
self.text = text;
|
||||
self.shaped = None;
|
||||
self.layout_key = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Invalidates the layout and returns the underlying string for editing.
|
||||
pub fn edit(&mut self) -> &mut String {
|
||||
self.shaped = None;
|
||||
self.layout_key = None;
|
||||
&mut self.text
|
||||
}
|
||||
|
||||
@@ -124,12 +134,16 @@ impl TextBuffer {
|
||||
}
|
||||
|
||||
pub fn shape(&mut self, data: &mut TextData, attrs: &TextAttrs, width: Option<f32>) {
|
||||
if self.shaped.as_ref() == Some(&(attrs.clone(), width)) {
|
||||
let layout_key = LayoutKey {
|
||||
attrs: attrs.clone(),
|
||||
max_width: width,
|
||||
};
|
||||
if self.layout_key.as_ref() == Some(&layout_key) {
|
||||
return;
|
||||
}
|
||||
let mut builder = data
|
||||
.layout_cx
|
||||
.ranged_builder(&mut data.font_cx, &self.text, 1.0, true);
|
||||
.layout_ctx
|
||||
.ranged_builder(&mut data.font_ctx, &self.text, 1.0, true);
|
||||
builder.push_default(StyleProperty::FontFamily(attrs.family.family()));
|
||||
builder.push_default(StyleProperty::FontSize(attrs.font_size));
|
||||
builder.push_default(StyleProperty::LineHeight(LineHeight::Absolute(
|
||||
@@ -140,7 +154,7 @@ impl TextBuffer {
|
||||
self.layout.break_all_lines(width);
|
||||
self.layout
|
||||
.align(Alignment::Start, AlignmentOptions::default());
|
||||
self.shaped = Some((attrs.clone(), width));
|
||||
self.layout_key = Some(layout_key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,8 +174,6 @@ impl TextData {
|
||||
continue;
|
||||
};
|
||||
let coords_hash = hash_coords(coords);
|
||||
// `font.data.id()` rather than the pointer, so the same font
|
||||
// loaded twice is still one set of entries.
|
||||
let font_id = font.data.id();
|
||||
|
||||
for glyph in run.positioned_glyphs() {
|
||||
@@ -169,38 +181,23 @@ impl TextData {
|
||||
let key = GlyphKey {
|
||||
font: font_id,
|
||||
glyph: glyph.id,
|
||||
size: (font_size * 16.0).round() as u32,
|
||||
size: glyph_size_key(font_size),
|
||||
subpixel,
|
||||
coords: coords_hash,
|
||||
};
|
||||
let entry = match self.atlas.get(&key) {
|
||||
Some(entry) => entry,
|
||||
None => {
|
||||
let mut scaler = self
|
||||
.scale_cx
|
||||
.builder(font_ref)
|
||||
.size(font_size)
|
||||
.hint(true)
|
||||
.normalized_coords(coords)
|
||||
.build();
|
||||
let image = Render::new(&[
|
||||
Source::ColorOutline(0),
|
||||
Source::ColorBitmap(StrikeWith::BestFit),
|
||||
Source::Outline,
|
||||
])
|
||||
.format(Format::Alpha)
|
||||
.offset(Vector::new(subpixel as f32 / 4.0, 0.0))
|
||||
.render(&mut scaler, glyph.id as u16);
|
||||
match image {
|
||||
Some(image) => self.atlas.insert(key, &image, textures),
|
||||
None => {
|
||||
self.atlas.insert_empty(key);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(entry) = self.glyph_entry(
|
||||
GlyphRaster {
|
||||
key,
|
||||
font: font_ref,
|
||||
font_size,
|
||||
coords,
|
||||
subpixel,
|
||||
glyph_id: glyph.id,
|
||||
},
|
||||
textures,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
let Some(entry) = entry else { continue };
|
||||
placed.push(PlacedGlyph {
|
||||
entry,
|
||||
offset: Vec2::new(
|
||||
@@ -213,20 +210,64 @@ impl TextData {
|
||||
}
|
||||
placed
|
||||
}
|
||||
|
||||
fn glyph_entry(
|
||||
&mut self,
|
||||
glyph: GlyphRaster<'_>,
|
||||
textures: &mut Textures,
|
||||
) -> Option<GlyphEntry> {
|
||||
if let Some(entry) = self.atlas.get(&glyph.key) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
let mut scaler = self
|
||||
.scale_ctx
|
||||
.builder(glyph.font)
|
||||
.size(glyph.font_size)
|
||||
.hint(true)
|
||||
.normalized_coords(glyph.coords)
|
||||
.build();
|
||||
let image = Render::new(&[
|
||||
Source::ColorOutline(0),
|
||||
Source::ColorBitmap(StrikeWith::BestFit),
|
||||
Source::Outline,
|
||||
])
|
||||
.format(Format::Alpha)
|
||||
.offset(Vector::new(glyph.subpixel as f32 / 4.0, 0.0))
|
||||
.render(&mut scaler, glyph.glyph_id as u16);
|
||||
|
||||
if let Some(image) = image {
|
||||
self.atlas.insert(glyph.key, &image, textures)
|
||||
} else {
|
||||
self.atlas.insert_empty(glyph.key);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GlyphRaster<'a> {
|
||||
key: GlyphKey,
|
||||
font: FontRef<'a>,
|
||||
font_size: f32,
|
||||
coords: &'a [i16],
|
||||
subpixel: u8,
|
||||
glyph_id: u32,
|
||||
}
|
||||
|
||||
fn hash_coords(coords: &[i16]) -> u64 {
|
||||
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
|
||||
for c in coords {
|
||||
h ^= *c as u16 as u64;
|
||||
h = h.wrapping_mul(0x1000_0000_01b3);
|
||||
}
|
||||
h
|
||||
let mut hasher = DefaultHasher::new();
|
||||
coords.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
const GLYPH_SIZE_STEPS_PER_PIXEL: f32 = 16.0;
|
||||
|
||||
fn glyph_size_key(font_size: f32) -> u32 {
|
||||
(font_size * GLYPH_SIZE_STEPS_PER_PIXEL).round() as u32
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RenderedText {
|
||||
pub glyphs: std::sync::Arc<Vec<PlacedGlyph>>,
|
||||
pub glyphs: Vec<PlacedGlyph>,
|
||||
pub size: Vec2,
|
||||
pub color: UiColor,
|
||||
}
|
||||
@@ -242,7 +283,7 @@ impl TextData {
|
||||
buffer.shape(self, attrs, width);
|
||||
let glyphs = self.place(buffer, textures);
|
||||
RenderedText {
|
||||
glyphs: std::sync::Arc::new(glyphs),
|
||||
glyphs,
|
||||
size: buffer.size(),
|
||||
color: attrs.color,
|
||||
}
|
||||
|
||||
+80
-48
@@ -27,18 +27,26 @@ pub struct GlyphKey {
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct GlyphEntry {
|
||||
pub uv_min: [f32; 2],
|
||||
pub uv_max: [f32; 2],
|
||||
pub uv_min: Vec2,
|
||||
pub uv_max: Vec2,
|
||||
/// Offset from the glyph's pen position to the top-left of its pixels.
|
||||
pub left: i32,
|
||||
pub top: i32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub is_color: bool,
|
||||
pub is_colored: bool,
|
||||
pub view_idx: u32,
|
||||
pub sampler_idx: u32,
|
||||
}
|
||||
|
||||
impl GlyphEntry {
|
||||
const IS_COLORED: u32 = 1;
|
||||
|
||||
pub(crate) fn flags(&self) -> u32 {
|
||||
if self.is_colored { Self::IS_COLORED } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
struct Page {
|
||||
handle: TextureHandle,
|
||||
x: u32,
|
||||
@@ -68,10 +76,20 @@ impl GlyphAtlas {
|
||||
let w = image.placement.width;
|
||||
let h = image.placement.height;
|
||||
if w == 0 || h == 0 {
|
||||
log::warn!(
|
||||
"glyph {} in font {} rasterized at {w}x{h}; skipping it",
|
||||
key.glyph,
|
||||
key.font,
|
||||
);
|
||||
self.entries.insert(key, None);
|
||||
return None;
|
||||
}
|
||||
if w + PAD * 2 > PAGE || h + PAD * 2 > PAGE {
|
||||
if w > PAGE - PAD * 2 || h > PAGE - PAD * 2 {
|
||||
log::warn!(
|
||||
"glyph {} in font {} rasterized at {w}x{h}, too large for the {PAGE}x{PAGE} atlas; skipping it",
|
||||
key.glyph,
|
||||
key.font,
|
||||
);
|
||||
self.entries.insert(key, None);
|
||||
return None;
|
||||
}
|
||||
@@ -95,13 +113,13 @@ impl GlyphAtlas {
|
||||
let page = &self.pages[page_idx];
|
||||
let scale = 1.0 / PAGE as f32;
|
||||
let entry = GlyphEntry {
|
||||
uv_min: [x as f32 * scale, y as f32 * scale],
|
||||
uv_max: [(x + w) as f32 * scale, (y + h) as f32 * scale],
|
||||
uv_min: Vec2::new(x as f32 * scale, y as f32 * scale),
|
||||
uv_max: Vec2::new((x + w) as f32 * scale, (y + h) as f32 * scale),
|
||||
left: image.placement.left,
|
||||
top: image.placement.top,
|
||||
width: w,
|
||||
height: h,
|
||||
is_color: matches!(image.content, Content::Color),
|
||||
is_colored: matches!(image.content, Content::Color),
|
||||
view_idx: page.handle.primitive().view_idx,
|
||||
sampler_idx: page.handle.primitive().sampler_idx,
|
||||
};
|
||||
@@ -110,18 +128,12 @@ impl GlyphAtlas {
|
||||
}
|
||||
|
||||
fn allocate(&mut self, w: u32, h: u32, textures: &mut Textures) -> (usize, u32, u32) {
|
||||
let need_w = w + PAD;
|
||||
let need_h = h + PAD;
|
||||
if let Some(i) = self.pages.iter().position(|p| fits(p, need_w, need_h)) {
|
||||
let page = &mut self.pages[i];
|
||||
if page.x + need_w > PAGE {
|
||||
page.y += page.shelf_height;
|
||||
page.x = PAD;
|
||||
page.shelf_height = 0;
|
||||
}
|
||||
let (x, y) = (page.x, page.y);
|
||||
page.x += need_w;
|
||||
page.shelf_height = page.shelf_height.max(need_h);
|
||||
if let Some((i, (x, y))) = self
|
||||
.pages
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.find_map(|(i, page)| page.allocate(w, h).map(|position| (i, position)))
|
||||
{
|
||||
return (i, x, y);
|
||||
}
|
||||
|
||||
@@ -148,45 +160,65 @@ impl GlyphAtlas {
|
||||
}
|
||||
}
|
||||
|
||||
fn fits(page: &Page, need_w: u32, need_h: u32) -> bool {
|
||||
(page.x + need_w <= PAGE && page.y + need_h <= PAGE)
|
||||
|| (need_w + PAD <= PAGE && page.y + page.shelf_height + need_h <= PAGE)
|
||||
impl Page {
|
||||
fn allocate(&mut self, w: u32, h: u32) -> Option<(u32, u32)> {
|
||||
let need_w = w + PAD;
|
||||
let need_h = h + PAD;
|
||||
if self.x + need_w > PAGE {
|
||||
if need_w + PAD > PAGE || self.y + self.shelf_height + need_h > PAGE {
|
||||
return None;
|
||||
}
|
||||
self.y += self.shelf_height;
|
||||
self.x = PAD;
|
||||
self.shelf_height = 0;
|
||||
} else if self.y + need_h > PAGE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let position = (self.x, self.y);
|
||||
self.x += need_w;
|
||||
self.shelf_height = self.shelf_height.max(need_h);
|
||||
Some(position)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mask glyphs keep coverage in alpha so their raster can be tinted at draw time.
|
||||
fn write_glyph(page: &mut RgbaImage, image: &Image, x: u32, y: u32) {
|
||||
let w = image.placement.width;
|
||||
let h = image.placement.height;
|
||||
let width = image.placement.width as usize;
|
||||
let height = image.placement.height as usize;
|
||||
let page_stride = page.width() as usize * 4;
|
||||
let x = x as usize * 4;
|
||||
let y = y as usize;
|
||||
let page = page.as_mut();
|
||||
|
||||
for row in 0..height {
|
||||
let start = (y + row) * page_stride + x;
|
||||
let target = &mut page[start..start + width * 4];
|
||||
match image.content {
|
||||
Content::Mask => {
|
||||
for row in 0..h {
|
||||
for col in 0..w {
|
||||
let a = image.data[(row * w + col) as usize];
|
||||
page.put_pixel(x + col, y + row, image::Rgba([255, 255, 255, a]));
|
||||
}
|
||||
}
|
||||
}
|
||||
Content::Color => {
|
||||
for row in 0..h {
|
||||
for col in 0..w {
|
||||
let i = ((row * w + col) * 4) as usize;
|
||||
let px = [
|
||||
image.data[i],
|
||||
image.data[i + 1],
|
||||
image.data[i + 2],
|
||||
image.data[i + 3],
|
||||
];
|
||||
page.put_pixel(x + col, y + row, image::Rgba(px));
|
||||
let start = row * width * 4;
|
||||
target.copy_from_slice(&image.data[start..start + width * 4]);
|
||||
}
|
||||
Content::Mask => {
|
||||
let start = row * width;
|
||||
for (target, &alpha) in target
|
||||
.as_chunks_mut::<4>()
|
||||
.0
|
||||
.iter_mut()
|
||||
.zip(&image.data[start..start + width])
|
||||
{
|
||||
target.copy_from_slice(&[255, 255, 255, alpha]);
|
||||
}
|
||||
}
|
||||
Content::SubpixelMask => {
|
||||
// Preserve readable output if the rasterizer returns a subpixel mask.
|
||||
for row in 0..h {
|
||||
for col in 0..w {
|
||||
let i = ((row * w + col) * 4) as usize;
|
||||
let a = image.data[i + 1];
|
||||
page.put_pixel(x + col, y + row, image::Rgba([255, 255, 255, a]));
|
||||
let start = row * width * 4;
|
||||
for (target, source) in target
|
||||
.as_chunks_mut::<4>()
|
||||
.0
|
||||
.iter_mut()
|
||||
.zip(image.data[start..start + width * 4].as_chunks::<4>().0)
|
||||
{
|
||||
target.copy_from_slice(&[255, 255, 255, source[1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::{
|
||||
ArrBuf,
|
||||
data::{MaskIdx, PrimitiveInstance},
|
||||
},
|
||||
util::Vec2,
|
||||
};
|
||||
use bytemuck::Pod;
|
||||
use wgpu::*;
|
||||
@@ -234,18 +235,14 @@ pub struct TexturePrimitive {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct GlyphPrimitive {
|
||||
pub uv_min: [f32; 2],
|
||||
pub uv_max: [f32; 2],
|
||||
pub uv_min: Vec2,
|
||||
pub uv_max: Vec2,
|
||||
pub view_idx: u32,
|
||||
pub sampler_idx: u32,
|
||||
pub color: Color<u8>,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
impl GlyphPrimitive {
|
||||
pub const IS_COLOR: u32 = 1;
|
||||
}
|
||||
|
||||
pub struct PrimitiveVec<T> {
|
||||
vec: Vec<T>,
|
||||
free: Vec<usize>,
|
||||
|
||||
+10
-10
@@ -17,36 +17,36 @@ pub struct GpuTextures {
|
||||
|
||||
impl GpuTextures {
|
||||
pub fn update(&mut self, textures: &mut Textures) -> bool {
|
||||
let mut changed = false;
|
||||
let mut bindings_changed = false;
|
||||
for update in textures.updates() {
|
||||
match update {
|
||||
bindings_changed |= match update {
|
||||
TextureUpdate::Push(image) => {
|
||||
self.push(image);
|
||||
changed = true;
|
||||
true
|
||||
}
|
||||
TextureUpdate::Set(i, image) => {
|
||||
self.set(i, image);
|
||||
changed = true;
|
||||
true
|
||||
}
|
||||
TextureUpdate::Patch(i, rect, image) => {
|
||||
// Patching contents leaves the binding array unchanged.
|
||||
self.patch(i, rect, image);
|
||||
false
|
||||
}
|
||||
TextureUpdate::SetFree => {
|
||||
self.view_count += 1;
|
||||
changed = true;
|
||||
true
|
||||
}
|
||||
TextureUpdate::Free(i) => {
|
||||
self.free(i);
|
||||
changed = true;
|
||||
true
|
||||
}
|
||||
TextureUpdate::PushFree => {
|
||||
self.push_free();
|
||||
changed = true;
|
||||
true
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
changed
|
||||
bindings_changed
|
||||
}
|
||||
fn set(&mut self, i: u32, image: &DynamicImage) {
|
||||
self.view_count += 1;
|
||||
|
||||
@@ -101,13 +101,6 @@ impl<'a> Painter<'a> {
|
||||
}
|
||||
|
||||
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
||||
let flags_for = |is_color| {
|
||||
if is_color {
|
||||
GlyphPrimitive::IS_COLOR
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
for glyph in text.glyphs.iter() {
|
||||
let mut region = origin;
|
||||
region.x.end = region.x.start;
|
||||
@@ -122,7 +115,7 @@ impl<'a> Painter<'a> {
|
||||
view_idx: glyph.entry.view_idx,
|
||||
sampler_idx: glyph.entry.sampler_idx,
|
||||
color: text.color,
|
||||
flags: flags_for(glyph.entry.is_color),
|
||||
flags: glyph.entry.flags(),
|
||||
},
|
||||
region,
|
||||
);
|
||||
|
||||
+16
-26
@@ -111,7 +111,7 @@ impl<'a> TextEditCtx<'a> {
|
||||
self.text.view.buf.layout()
|
||||
}
|
||||
|
||||
fn refresh(&mut self) {
|
||||
fn clamp_selection_to_layout(&mut self) {
|
||||
if let Some(sel) = self.text.selection {
|
||||
let layout = self.layout();
|
||||
self.text.selection = Some(sel.refresh(layout));
|
||||
@@ -119,8 +119,8 @@ impl<'a> TextEditCtx<'a> {
|
||||
}
|
||||
|
||||
pub fn take(&mut self) -> String {
|
||||
let text = self.text.view.buf.text().to_string();
|
||||
self.set("");
|
||||
let text = std::mem::take(self.text.view.buf.edit());
|
||||
self.text.selection = None;
|
||||
text
|
||||
}
|
||||
|
||||
@@ -219,9 +219,8 @@ impl<'a> TextEditCtx<'a> {
|
||||
if end == 0 {
|
||||
return;
|
||||
}
|
||||
let start = {
|
||||
let layout = self.layout();
|
||||
if word {
|
||||
let start = if word {
|
||||
sel.focus().previous_logical_word(layout).index()
|
||||
} else {
|
||||
let Some(cluster) = sel.focus().logical_clusters(layout)[0] else {
|
||||
@@ -236,7 +235,6 @@ impl<'a> TextEditCtx<'a> {
|
||||
.next_back()
|
||||
.map_or(range.start, |(start, _)| start)
|
||||
}
|
||||
}
|
||||
};
|
||||
self.delete_range(start, end);
|
||||
}
|
||||
@@ -252,9 +250,8 @@ impl<'a> TextEditCtx<'a> {
|
||||
if start >= self.text.view.buf.text().len() {
|
||||
return;
|
||||
}
|
||||
let end = {
|
||||
let layout = self.layout();
|
||||
if word {
|
||||
let end = if word {
|
||||
sel.focus().next_logical_word(layout).index()
|
||||
} else {
|
||||
let clusters = sel.focus().logical_clusters(layout);
|
||||
@@ -262,17 +259,11 @@ impl<'a> TextEditCtx<'a> {
|
||||
return;
|
||||
};
|
||||
cluster.text_range().end
|
||||
}
|
||||
};
|
||||
self.delete_range(start, end);
|
||||
}
|
||||
|
||||
fn delete_range(&mut self, start: usize, end: usize) {
|
||||
let len = self.text.view.buf.text().len();
|
||||
let (start, end) = (start.min(end).min(len), start.max(end).min(len));
|
||||
if start == end {
|
||||
return;
|
||||
}
|
||||
self.text.view.buf.edit().replace_range(start..end, "");
|
||||
self.text.view.buf.changed = true;
|
||||
self.set_caret(start);
|
||||
@@ -294,32 +285,31 @@ impl<'a> TextEditCtx<'a> {
|
||||
let prev_sel = self.text.selection;
|
||||
let prev_hit = self.text.double_hit;
|
||||
|
||||
let outcome = {
|
||||
let layout = self.layout();
|
||||
if drag {
|
||||
prev_sel.map(|sel| (Some(sel.extend_to_point(layout, pos.x, pos.y)), prev_hit))
|
||||
let (selection, double_hit) = if drag {
|
||||
let Some(selection) = prev_sel else {
|
||||
return;
|
||||
};
|
||||
(selection.extend_to_point(layout, pos.x, pos.y), prev_hit)
|
||||
} else {
|
||||
let hit = Selection::from_point(layout, pos.x, pos.y);
|
||||
let index = hit.focus().index();
|
||||
// Successive clicks at one index select the word, then the line.
|
||||
Some(if recent && prev_hit == Some(index) {
|
||||
(Some(Selection::line_from_point(layout, pos.x, pos.y)), None)
|
||||
if recent && prev_hit == Some(index) {
|
||||
(Selection::line_from_point(layout, pos.x, pos.y), None)
|
||||
} else if recent && prev_sel.map(|s| s.focus().index()) == Some(index) {
|
||||
(
|
||||
Some(Selection::word_from_point(layout, pos.x, pos.y)),
|
||||
Selection::word_from_point(layout, pos.x, pos.y),
|
||||
Some(index),
|
||||
)
|
||||
} else {
|
||||
(Some(hit), None)
|
||||
})
|
||||
(hit, None)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((selection, double_hit)) = outcome {
|
||||
self.text.selection = selection;
|
||||
self.text.selection = Some(selection);
|
||||
self.text.double_hit = double_hit;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deselect(&mut self) {
|
||||
self.text.selection = None;
|
||||
@@ -334,7 +324,7 @@ impl<'a> TextEditCtx<'a> {
|
||||
if let Some((old, selection)) = self.text.history.pop() {
|
||||
self.set(&old);
|
||||
self.text.selection = selection;
|
||||
self.refresh();
|
||||
self.clamp_selection_to_layout();
|
||||
}
|
||||
} else if self.text.view.buf.text() != old.0 {
|
||||
self.text.history.push(old);
|
||||
|
||||
+19
-22
@@ -23,7 +23,7 @@ pub struct TextView {
|
||||
}
|
||||
|
||||
impl TextView {
|
||||
fn is_blank(&self) -> bool {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.buf.is_empty()
|
||||
}
|
||||
|
||||
@@ -52,31 +52,25 @@ impl TextView {
|
||||
.align(self.align)
|
||||
}
|
||||
|
||||
fn render(&mut self, ctx: &mut SizeCtx) -> RenderedText {
|
||||
fn render(&mut self, ctx: &mut SizeCtx) -> &RenderedText {
|
||||
let width = if self.attrs.wrap {
|
||||
Some(ctx.px_size().x)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if width == self.width
|
||||
&& let Some(tex) = &self.tex
|
||||
&& !self.attrs.changed
|
||||
&& !self.buf.changed
|
||||
{
|
||||
return tex.clone();
|
||||
}
|
||||
if width != self.width || self.tex.is_none() || self.attrs.changed || self.buf.changed {
|
||||
self.width = width;
|
||||
let tex = ctx.draw_text(&mut self.buf, &self.attrs, width);
|
||||
self.tex = Some(tex.clone());
|
||||
self.tex = Some(ctx.draw_text(&mut self.buf, &self.attrs, width));
|
||||
self.attrs.changed = false;
|
||||
self.buf.changed = false;
|
||||
tex
|
||||
}
|
||||
self.tex.as_ref().unwrap()
|
||||
}
|
||||
pub fn tex(&self) -> Option<&RenderedText> {
|
||||
self.tex.as_ref()
|
||||
}
|
||||
pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||
if self.is_blank()
|
||||
if self.is_empty()
|
||||
&& let Some(hint) = &self.hint
|
||||
{
|
||||
ctx.width(hint)
|
||||
@@ -85,7 +79,7 @@ impl TextView {
|
||||
}
|
||||
}
|
||||
pub fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||
if self.is_blank()
|
||||
if self.is_empty()
|
||||
&& let Some(hint) = &self.hint
|
||||
{
|
||||
ctx.height(hint)
|
||||
@@ -94,16 +88,19 @@ impl TextView {
|
||||
}
|
||||
}
|
||||
pub fn draw(&mut self, painter: &mut Painter) -> UiRegion {
|
||||
let tex = self.render(&mut painter.size_ctx());
|
||||
let region = tex.size.align(self.align);
|
||||
if self.is_blank()
|
||||
&& let Some(hint) = &self.hint
|
||||
{
|
||||
let align = self.align;
|
||||
if self.is_empty() && self.hint.is_some() {
|
||||
let region = self.render(&mut painter.size_ctx()).size.align(align);
|
||||
if let Some(hint) = &self.hint {
|
||||
painter.widget(hint);
|
||||
} else {
|
||||
let within = region.within(&painter.region());
|
||||
painter.glyphs(&tex, within);
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
let tex = self.render(&mut painter.size_ctx());
|
||||
let region = tex.size.align(align);
|
||||
let within = region.within(&painter.region());
|
||||
painter.glyphs(tex, within);
|
||||
region
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user