mirror of
https://github.com/danbulant/mangui
synced 2026-06-19 22:31:03 +00:00
improved text rendering, again
This commit is contained in:
parent
24481bed2a
commit
455fad5269
7 changed files with 115 additions and 188 deletions
|
|
@ -1,81 +0,0 @@
|
|||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use mangui::{SharedNode, nodes::{layout::Layout, Style}, taffy::prelude::Size};
|
||||
use rusalka::{component::Component, nodes::{primitives::{Rectangle, RectangleAttributes}, insert, detach}, SharedComponent, WeakSharedComponent, invalidator::Invalidator};
|
||||
|
||||
pub struct DemoComponent {
|
||||
rect: SharedComponent<Rectangle>,
|
||||
attrs: DemoComponentAttributes,
|
||||
layout: Arc<RwLock<Layout>>,
|
||||
selfref: WeakSharedComponent<Self>,
|
||||
test: Arc<Mutex<Invalidator<bool>>>
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DemoComponentAttributes {}
|
||||
#[derive(Default)]
|
||||
pub struct PartialDemoComponentAttributes {}
|
||||
|
||||
impl From<DemoComponentAttributes> for PartialDemoComponentAttributes {
|
||||
fn from(_attrs: DemoComponentAttributes) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Component for DemoComponent {
|
||||
type ComponentAttrs = DemoComponentAttributes;
|
||||
type PartialComponentAttrs = PartialDemoComponentAttributes;
|
||||
fn new(attrs: Self::ComponentAttrs, selfref: WeakSharedComponent<Self>) -> Self {
|
||||
let test = Arc::new(Mutex::new(Invalidator::new(false)));
|
||||
let this = Self {
|
||||
rect: Arc::new_cyclic(|selfref| Mutex::new(Rectangle::new(RectangleAttributes { ..Default::default() }, selfref.clone()))),
|
||||
layout: Arc::new(RwLock::new(Layout {
|
||||
style: Style {
|
||||
layout: mangui::nodes::TaffyStyle {
|
||||
min_size: Size {
|
||||
width: mangui::taffy::style::Dimension::Length( if **test.lock().unwrap() { 50. } else { 100. }),
|
||||
height: mangui::taffy::style::Dimension::Length(100.)
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})),
|
||||
attrs,
|
||||
selfref,
|
||||
test
|
||||
};
|
||||
let selfref = this.selfref.clone();
|
||||
this.layout.write().unwrap().events.add_handler(Box::new(move |event| {
|
||||
let selfref = selfref.upgrade().unwrap();
|
||||
let this = selfref.lock().unwrap();
|
||||
let attrs = &this.attrs;
|
||||
let test = &this.test;
|
||||
match event.event {
|
||||
mangui::events::InnerEvent::MouseDown(_) => {
|
||||
**test.lock().unwrap() = true;
|
||||
},
|
||||
mangui::events::InnerEvent::MouseUp(_) => {
|
||||
**test.lock().unwrap() = false;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}));
|
||||
this
|
||||
}
|
||||
|
||||
fn set(&mut self, _attrs: Self::PartialComponentAttrs) { }
|
||||
fn get(&self) -> &Self::ComponentAttrs { &self.attrs }
|
||||
fn mount(&self, parent: &SharedNode, before: Option<&SharedNode>) {
|
||||
insert(parent, &{self.layout.clone()}, before);
|
||||
self.rect.lock().unwrap().mount(&{self.layout.clone()}, None);
|
||||
}
|
||||
|
||||
fn unmount(&self) {
|
||||
self.rect.lock().unwrap().unmount();
|
||||
detach(&{self.layout.clone()});
|
||||
}
|
||||
|
||||
fn update(&self, _bitmap: &[u32]) {}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ use std::default::Default;
|
|||
use mangui::{femtovg::{ImageFlags, Color, Paint}, cosmic_text::Metrics, nodes::{layout::Layout, Style}, nodes::text::Text, nodes::image::Image, taffy::prelude::Size};
|
||||
use mangui::nodes::TaffyStyle;
|
||||
use mangui::taffy::Display::Block;
|
||||
use mangui::taffy::{FlexDirection, LengthPercentage, Rect};
|
||||
|
||||
use rusalka::nodes::primitives::{Rectangle, RectangleAttributes, PartialRectangleAttributes};
|
||||
|
||||
|
|
@ -27,10 +28,27 @@ make_component!(
|
|||
Component {
|
||||
@layout {
|
||||
@Rectangle {
|
||||
radius: if $test_ { attrs.radius } else { 0. },
|
||||
..Default::default()
|
||||
radius: if $test_ { attrs.radius } else { 0. }
|
||||
}
|
||||
@layout {
|
||||
@text {
|
||||
text: String::from("Hello, World 🌎! And there's more text in here, as a single line"),
|
||||
metrics: Metrics::new(20., 25.),
|
||||
paint: Paint::color(Color::rgb(0, 255, 0)),
|
||||
style: Style {
|
||||
layout: TaffyStyle {
|
||||
padding: Rect {
|
||||
top: LengthPercentage::Length(10.),
|
||||
left: LengthPercentage::Length(10.),
|
||||
right: LengthPercentage::Length(10.),
|
||||
bottom: LengthPercentage::Length(10.)
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
@image {
|
||||
style: Style {
|
||||
layout: TaffyStyle {
|
||||
|
|
@ -46,16 +64,11 @@ make_component!(
|
|||
radius: 5.,
|
||||
..Default::default()
|
||||
}
|
||||
@text {
|
||||
text: String::from("Hello, World!"),
|
||||
metrics: Metrics::new(20., 25.),
|
||||
paint: Paint::color(Color::rgb(0, 255, 0)),
|
||||
..Default::default()
|
||||
}
|
||||
|
||||
style: Style {
|
||||
layout: TaffyStyle {
|
||||
display: Block,
|
||||
flex_direction: FlexDirection::Column,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use std::sync::{RwLock, Arc, mpsc, Mutex};
|
||||
|
||||
use mangui::{self, nodes::{layout::Layout, self, Style, TaffyStyle}, taffy::{self, prelude::Size, style::Dimension}, femtovg::{Paint, Color}, SharedNode, MainEntry, events::NodeEvent};
|
||||
use mangui::{self, nodes::layout::Layout, SharedNode, MainEntry};
|
||||
|
||||
mod component_demo;
|
||||
mod component_demo_syntax;
|
||||
mod anilist;
|
||||
|
||||
|
|
@ -11,38 +10,7 @@ use rusalka::component::Component;
|
|||
fn main() {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let _tx = Arc::new(tx);
|
||||
let mut root = Layout::default();
|
||||
root.style.layout.display = taffy::style::Display::Flex;
|
||||
root.style.layout.flex_direction = taffy::style::FlexDirection::Row;
|
||||
let right_node = Arc::new(RwLock::new(nodes::primitives::Rectangle {
|
||||
style: Style {
|
||||
layout: TaffyStyle {
|
||||
min_size: Size {
|
||||
width: Dimension::Length(50.),
|
||||
height: Dimension::Length(100.)
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
cursor: Default::default()
|
||||
},
|
||||
fill: Paint::color(Color::rgb(0, 0, 255)),
|
||||
radius: 0.,
|
||||
events: Default::default(),
|
||||
parent: None
|
||||
}));
|
||||
root.children.push(right_node.clone());
|
||||
right_node.clone().write().unwrap().events.add_handler(Box::new(move |event| {
|
||||
let NodeEvent { target, path, event } = event;
|
||||
match event {
|
||||
mangui::events::InnerEvent::MouseDown(_) => {
|
||||
right_node.write().unwrap().fill = Paint::color(Color::rgb(255, 0, 255));
|
||||
},
|
||||
mangui::events::InnerEvent::MouseUp(_) => {
|
||||
right_node.write().unwrap().fill = Paint::color(Color::rgb(0, 0, 255));
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}));
|
||||
let root = Layout::default();
|
||||
let groot: SharedNode = Arc::new(RwLock::new(root));
|
||||
|
||||
let cdemo: Arc<Mutex<component_demo_syntax::ComponentDemo>> = Arc::new_cyclic(|cself|
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use std::collections::HashMap;
|
|||
use std::num::NonZeroU32;
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, Mutex, RwLock, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use cosmic_text::FontSystem;
|
||||
use events::{Location, MouseValue, NodeEvent, MouseEvent};
|
||||
|
|
@ -14,7 +13,7 @@ use glutin_winit::DisplayBuilder;
|
|||
use nodes::{get_element_at, run_event_handlers, run_single_event_handlers};
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
use winit::event::{Event, WindowEvent, Modifiers, DeviceId};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::window::WindowBuilder;
|
||||
use winit::{dpi::PhysicalSize, window::Window};
|
||||
|
||||
|
|
@ -79,8 +78,8 @@ pub fn run_event_loop(entry: MainEntry) -> () {
|
|||
let mut taffy = TaffyTree::new();
|
||||
let mut taffy_map = NodeLayoutMap::new();
|
||||
{
|
||||
let clonned = entry.root.clone();
|
||||
let root = clonned.read().unwrap();
|
||||
let cloned = entry.root.clone();
|
||||
let root = cloned.read().unwrap();
|
||||
let root_style = root.deref().style();
|
||||
let root_layout = root_style.layout.to_owned();
|
||||
let taffy_root_node = taffy.new_leaf(root_layout).unwrap();
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ pub trait Node: Debug {
|
|||
|
||||
/// Render the node, called before rendering it's children
|
||||
/// Canvas considers 0, 0 to be top left corner (for location after layouting happens)
|
||||
/// Top left corner is after margin and similar, but before padding and border.
|
||||
fn render_pre_children(&mut self, _context: &mut RenderContext, _layout: Layout) {}
|
||||
/// Render the node, called after rendering it's children
|
||||
/// Canvas considers 0, 0 to be top left corner (for location after layouting happens)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use std::fmt::Debug;
|
||||
use crate::{events::handler::EventHandlerDatabase, SharedNode, WeakNode, FONT_SYSTEM};
|
||||
use super::{text_render_cache::RENDER_CACHE, Node, NodeChildren, Style, MeasureContext, RenderContext};
|
||||
use cosmic_text::{Attrs, Buffer, Metrics, Shaping};
|
||||
use cosmic_text::{Attrs, Buffer, Family, Metrics, Shaping, Stretch};
|
||||
use femtovg::{Color, Paint, Path};
|
||||
use taffy::{AvailableSpace, Size};
|
||||
use crate::nodes::text_render_cache::TextConfig;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Text {
|
||||
|
|
@ -38,7 +39,12 @@ impl Node for Text {
|
|||
// this can crash, but it should crash earlier during measure -> see the comment there.
|
||||
let buf = self.buffer.as_mut().unwrap();
|
||||
let mut font = FONT_SYSTEM.lock().unwrap();
|
||||
buf.set_size(&mut font, layout.size.width, layout.size.height);
|
||||
let offset_size = (
|
||||
layout.padding.left + layout.padding.right + layout.border.left + layout.border.right,
|
||||
layout.padding.top + layout.padding.bottom + layout.border.top + layout.border.bottom
|
||||
);
|
||||
// the height * scale factor is an ugly hack to fix height of the text... not sure why it's wrong in the first place
|
||||
buf.set_size(&mut font, layout.content_size.width - offset_size.0, (layout.content_size.height * context.scale_factor) - offset_size.1);
|
||||
buf.set_metrics(&mut font, self.metrics.scale(context.scale_factor));
|
||||
// fill_to_cmds requires FONT_SYSTEM lock.
|
||||
drop(font);
|
||||
|
|
@ -54,10 +60,26 @@ impl Node for Text {
|
|||
&path,
|
||||
&Paint::color(Color::rgb(255, 0, 0))
|
||||
);
|
||||
let position = (
|
||||
layout.padding.left + layout.border.left,
|
||||
layout.padding.top + layout.border.top
|
||||
);
|
||||
let mut path = Path::new();
|
||||
path.rounded_rect(
|
||||
position.0,
|
||||
position.1,
|
||||
layout.content_size.width - offset_size.0,
|
||||
layout.content_size.height - offset_size.1,
|
||||
0.
|
||||
);
|
||||
context.canvas.fill_path(
|
||||
&path,
|
||||
&Paint::color(Color::rgb(0, 0, 255))
|
||||
);
|
||||
let cmds = RENDER_CACHE.lock().unwrap()
|
||||
.fill_to_cmds(&mut context.canvas, buf, (0.0, 0.0), context.scale_factor)
|
||||
.fill_to_cmds(&mut context.canvas, buf, position, context.scale_factor, TextConfig { hint: false, subpixel: false })
|
||||
.unwrap();
|
||||
context.canvas.draw_glyph_commands(cmds, &self.paint, 1.0);
|
||||
context.canvas.draw_glyph_commands(cmds, &self.paint, context.scale_factor);
|
||||
}
|
||||
|
||||
fn measure(&mut self, context: &mut MeasureContext, known_dimensions: Size<Option<f32>>, available_space: Size<AvailableSpace>) -> Size<f32> {
|
||||
|
|
@ -74,7 +96,7 @@ impl Node for Text {
|
|||
buf.set_metrics(&mut font, self.metrics.scale(context.scale_factor));
|
||||
|
||||
// Compute layout
|
||||
buf.shape_until_scroll(&mut font, false);
|
||||
buf.shape_until_scroll(&mut font, true);
|
||||
drop(font);
|
||||
|
||||
// Determine measured size of text
|
||||
|
|
@ -82,7 +104,7 @@ impl Node for Text {
|
|||
.layout_runs()
|
||||
.fold((0.0, 0usize), |(width, total_lines), run| (run.line_w.max(width), total_lines + 1));
|
||||
// fixes text not rendering in some cases (??????)
|
||||
let height = total_lines as f32 * buf.metrics().line_height + 1.0;
|
||||
let height = (total_lines as f32 * buf.metrics().line_height + 1.0) / context.scale_factor;
|
||||
// fixes flickering of text on devices with non-integer scale factors due to loss of precision
|
||||
let width = width + 0.5;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,12 @@ pub struct FontTexture {
|
|||
image_id: ImageId
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct TextConfig {
|
||||
pub hint: bool,
|
||||
pub subpixel: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct RenderedGlyph {
|
||||
texture_index: usize,
|
||||
|
|
@ -57,25 +63,19 @@ impl RenderCache {
|
|||
buffer: &Buffer,
|
||||
position: (f32, f32),
|
||||
scale: f32,
|
||||
config: TextConfig
|
||||
) -> Result<GlyphDrawCommands, ErrorKind> {
|
||||
let mut alpha_cmd_map = HashMap::new();
|
||||
let mut color_cmd_map = HashMap::new();
|
||||
|
||||
//let total_height = buffer.layout_runs().len() as i32 * buffer.metrics().line_height;
|
||||
let lines = buffer.layout_runs().filter(|run| run.line_w != 0.0).count();
|
||||
let total_height = lines as f32 * buffer.metrics().line_height;
|
||||
for run in buffer.layout_runs() {
|
||||
for glyph in run.glyphs.iter() {
|
||||
let physical = glyph.physical(position, scale);
|
||||
let mut cache_key = physical.cache_key;
|
||||
let position_x = position.0 + cache_key.x_bin.as_float();
|
||||
let position_y = position.1 + cache_key.y_bin.as_float();
|
||||
//let position_x = position_x - run.line_w * justify.0;
|
||||
//let position_y = position_y - total_height as f32 * justify.1;
|
||||
let (position_x, subpixel_x) = SubpixelBin::new(position_x);
|
||||
let (position_y, subpixel_y) = SubpixelBin::new(position_y);
|
||||
cache_key.x_bin = subpixel_x;
|
||||
cache_key.y_bin = subpixel_y;
|
||||
let physical_glyph = glyph.physical(position, scale);
|
||||
let mut cache_key = physical_glyph.cache_key;
|
||||
// perform cache lookup for rendered glyph
|
||||
if let Some(rendered) = self.rendered_glyphs.entry(cache_key).or_insert_with(|| {
|
||||
let Some(rendered) = self.rendered_glyphs.entry(cache_key).or_insert_with(|| {
|
||||
// ...or insert it
|
||||
|
||||
// do the actual rasterization
|
||||
|
|
@ -86,7 +86,7 @@ impl RenderCache {
|
|||
.scale_context
|
||||
.builder(font.as_swash())
|
||||
.size(f32::from_bits(cache_key.font_size_bits))
|
||||
.hint(true)
|
||||
.hint(config.hint)
|
||||
.build();
|
||||
let offset = Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());
|
||||
let rendered = Render::new(&[
|
||||
|
|
@ -94,7 +94,7 @@ impl RenderCache {
|
|||
Source::ColorBitmap(StrikeWith::BestFit),
|
||||
Source::Outline,
|
||||
])
|
||||
.format(Format::Alpha)
|
||||
.format(if config.subpixel { Format::Subpixel } else { Format::Alpha })
|
||||
.offset(offset)
|
||||
.render(&mut scaler, cache_key.glyph_id);
|
||||
|
||||
|
|
@ -114,26 +114,32 @@ impl RenderCache {
|
|||
break;
|
||||
}
|
||||
}
|
||||
let (texture_index, atlas_alloc_x, atlas_alloc_y) = found.unwrap_or_else(|| {
|
||||
// if no atlas could fit the texture, make a new atlas tyvm
|
||||
// TODO error handling
|
||||
let mut atlas = Atlas::new(TEXTURE_SIZE, TEXTURE_SIZE);
|
||||
let image_id = canvas
|
||||
.create_image(
|
||||
Img::new(
|
||||
vec![RGBA8::new(0, 0, 0, 0); TEXTURE_SIZE * TEXTURE_SIZE],
|
||||
TEXTURE_SIZE,
|
||||
TEXTURE_SIZE,
|
||||
|
||||
let (texture_index, atlas_alloc_x, atlas_alloc_y) =
|
||||
found.unwrap_or_else(|| {
|
||||
// if no atlas could fit the texture, make a new atlas tyvm
|
||||
// TODO error handling
|
||||
let mut atlas = Atlas::new(TEXTURE_SIZE, TEXTURE_SIZE);
|
||||
let image_id = canvas
|
||||
.create_image(
|
||||
Img::new(
|
||||
vec![
|
||||
RGBA8::new(0, 0, 0, 0);
|
||||
TEXTURE_SIZE * TEXTURE_SIZE
|
||||
],
|
||||
TEXTURE_SIZE,
|
||||
TEXTURE_SIZE,
|
||||
)
|
||||
.as_ref(),
|
||||
ImageFlags::empty(),
|
||||
)
|
||||
.as_ref(),
|
||||
ImageFlags::empty(),
|
||||
)
|
||||
.unwrap();
|
||||
let texture_index = self.glyph_textures.len();
|
||||
let (x, y) = atlas.add_rect(alloc_w as usize, alloc_h as usize).unwrap();
|
||||
self.glyph_textures.push(FontTexture { atlas, image_id });
|
||||
(texture_index, x, y)
|
||||
});
|
||||
.unwrap();
|
||||
let texture_index = self.glyph_textures.len();
|
||||
let (x, y) =
|
||||
atlas.add_rect(alloc_w as usize, alloc_h as usize).unwrap();
|
||||
self.glyph_textures.push(FontTexture { atlas, image_id });
|
||||
(texture_index, x, y)
|
||||
});
|
||||
|
||||
let atlas_used_x = atlas_alloc_x as u32 + GLYPH_MARGIN;
|
||||
let atlas_used_y = atlas_alloc_y as u32 + GLYPH_MARGIN;
|
||||
|
|
@ -174,33 +180,32 @@ impl RenderCache {
|
|||
color_glyph: matches!(rendered.content, Content::Color),
|
||||
}
|
||||
})
|
||||
}) {
|
||||
let cmd_map = if rendered.color_glyph {
|
||||
&mut color_cmd_map
|
||||
} else {
|
||||
&mut alpha_cmd_map
|
||||
};
|
||||
}) else { continue };
|
||||
|
||||
let cmd = cmd_map.entry(rendered.texture_index).or_insert_with(|| DrawCommand {
|
||||
image_id: self.glyph_textures[rendered.texture_index].image_id,
|
||||
quads: Vec::new(),
|
||||
});
|
||||
let cmd_map = if rendered.color_glyph {
|
||||
&mut color_cmd_map
|
||||
} else {
|
||||
&mut alpha_cmd_map
|
||||
};
|
||||
|
||||
let mut q = Quad::default();
|
||||
let it = 1.0 / TEXTURE_SIZE as f32;
|
||||
let cmd = cmd_map.entry(rendered.texture_index).or_insert_with(|| DrawCommand {
|
||||
image_id: self.glyph_textures[rendered.texture_index].image_id,
|
||||
quads: Vec::new(),
|
||||
});
|
||||
|
||||
q.x0 = (position_x + glyph.x as i32 + rendered.offset_x - GLYPH_PADDING as i32) as f32;
|
||||
q.y0 = (position_y + run.line_y as i32 + glyph.y as i32 - rendered.offset_y - GLYPH_PADDING as i32) as f32;
|
||||
q.x1 = q.x0 + rendered.width as f32;
|
||||
q.y1 = q.y0 + rendered.height as f32;
|
||||
let mut q = Quad::default();
|
||||
let it = 1.0 / TEXTURE_SIZE as f32;
|
||||
q.x0 = (physical_glyph.x + rendered.offset_x - GLYPH_PADDING as i32) as f32;
|
||||
q.y0 = (physical_glyph.y - rendered.offset_y - GLYPH_PADDING as i32) as f32 + run.line_y;
|
||||
q.x1 = q.x0 + rendered.width as f32;
|
||||
q.y1 = q.y0 + rendered.height as f32;
|
||||
|
||||
q.s0 = rendered.atlas_x as f32 * it;
|
||||
q.t0 = rendered.atlas_y as f32 * it;
|
||||
q.s1 = (rendered.atlas_x + rendered.width) as f32 * it;
|
||||
q.t1 = (rendered.atlas_y + rendered.height) as f32 * it;
|
||||
q.s0 = rendered.atlas_x as f32 * it;
|
||||
q.t0 = rendered.atlas_y as f32 * it;
|
||||
q.s1 = (rendered.atlas_x + rendered.width) as f32 * it;
|
||||
q.t1 = (rendered.atlas_y + rendered.height) as f32 * it;
|
||||
|
||||
cmd.quads.push(q);
|
||||
}
|
||||
cmd.quads.push(q);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue