snap text on shader

This commit is contained in:
2025-08-23 22:16:00 -04:00
parent 5ce6fca275
commit 50ccf7393d
10 changed files with 101 additions and 64 deletions

View File

@@ -2,16 +2,33 @@ use crate::prelude::*;
pub struct Text {
content: String,
attrs: TextAttrs,
}
impl Text {
pub fn size(mut self, size: impl UiNum) -> Self {
self.attrs.size = size.to_f32();
self
}
pub fn color(mut self, color: UiColor) -> Self {
self.attrs.color = color;
self
}
pub fn line_height(mut self, height: f32) -> Self {
self.attrs.line_height = height;
self
}
}
impl<Ctx> Widget<Ctx> for Text {
fn draw(&self, painter: &mut Painter<Ctx>) {
painter.draw_text(&self.content);
painter.draw_text(&self.content, &self.attrs);
}
}
pub fn text(text: impl Into<String>) -> Text {
Text {
content: text.into(),
attrs: TextAttrs::default(),
}
}

View File

@@ -1,7 +1,7 @@
use std::{any::TypeId, marker::PhantomData};
use std::{any::TypeId, marker::PhantomData, sync::mpsc::Sender};
use crate::{
layout::{FnTag, Ui, UiMsg, UiMsgSender, Widget, WidgetLike, WidgetTag},
layout::{FnTag, Ui, Widget, WidgetLike, WidgetTag},
util::{Id, RefCounter},
};
@@ -18,7 +18,7 @@ pub struct WidgetId<W = AnyWidget> {
pub(super) ty: TypeId,
pub(super) id: Id,
counter: RefCounter,
send: UiMsgSender,
send: Sender<Id>,
_pd: PhantomData<W>,
}
@@ -41,7 +41,7 @@ impl<W> Clone for WidgetId<W> {
}
impl<W> WidgetId<W> {
pub(super) fn new(id: Id, ty: TypeId, send: UiMsgSender) -> Self {
pub(super) fn new(id: Id, ty: TypeId, send: Sender<Id>) -> Self {
Self {
ty,
id,
@@ -73,7 +73,7 @@ impl<W> WidgetId<W> {
impl<W> Drop for WidgetId<W> {
fn drop(&mut self) {
if self.counter.drop() {
let _ = self.send.send(UiMsg::FreeWidget(self.id.duplicate()));
let _ = self.send.send(self.id.duplicate());
}
}
}

View File

@@ -2,8 +2,8 @@ use image::GenericImageView;
use crate::{
layout::{
ActiveSensors, SensorMap, TextData, TextureHandle, Textures, UiPos, UiRegion, Vec2,
WidgetId, Widgets,
ActiveSensors, SensorMap, TextAttrs, TextData, TextureHandle, Textures, UiPos, UiRegion,
Vec2, WidgetId, Widgets,
},
render::{Primitive, Primitives},
};
@@ -21,6 +21,7 @@ pub struct Painter<'a, Ctx: 'static> {
}
impl<'a, Ctx> Painter<'a, Ctx> {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
nodes: &'a Widgets<Ctx>,
primitives: &'a mut Primitives,
@@ -94,16 +95,14 @@ impl<'a, Ctx> Painter<'a, Ctx> {
self.region = old;
}
pub fn draw_text(&mut self, content: &str) {
let handle = self.text.draw(content, self.textures);
pub fn draw_text(&mut self, content: &str, attrs: &TextAttrs) {
let handle = self.text.draw(content, attrs, self.textures);
let dims: Vec2 = self.textures[&handle].dimensions().into();
let center = self.region.center().snap(self.screen_size);
let top_left = center - (dims / 2.0).floor();
let bot_right = center + (dims / 2.0).ceil();
let region = UiRegion {
top_left: UiPos::offset(top_left),
bot_right: UiPos::offset(bot_right),
};
let mut region = self.region.center().expand(dims);
// TODO: I feel like this shouldn't be needed
// what this does is makes sure the text doesn't get squeezed into one pixel less
// I'm unsure exactly why it happens or if this will ever expand it too much
region.bot_right.shift(0.01);
self.draw_texture_at(&handle, region);
}

View File

@@ -42,8 +42,8 @@ impl UiPos {
Self::anchor(corner.anchor())
}
pub const fn shift(&mut self, offset: Vec2) {
self.offset += offset;
pub const fn shift(&mut self, offset: impl const Into<Vec2>) {
self.offset += offset.into();
}
pub const fn shifted(mut self, offset: Vec2) -> Self {

View File

@@ -3,26 +3,48 @@ use image::{Rgba, RgbaImage};
use crate::{
HashMap,
layout::{TextureHandle, Textures},
layout::{TextureHandle, Textures, UiColor},
};
pub(crate) struct TextData {
font_system: FontSystem,
cache: SwashCache,
swash_cache: SwashCache,
}
impl Default for TextData {
fn default() -> Self {
Self {
font_system: FontSystem::new(),
cache: SwashCache::new(),
swash_cache: SwashCache::new(),
}
}
}
pub struct TextAttrs {
pub color: UiColor,
pub size: f32,
pub line_height: f32,
}
impl Default for TextAttrs {
fn default() -> Self {
let size = 14.0;
Self {
color: UiColor::WHITE,
size,
line_height: size * 1.2,
}
}
}
impl TextData {
pub fn draw(&mut self, content: &str, textures: &mut Textures) -> TextureHandle {
let metrics = Metrics::new(30.0, 30.0);
pub fn draw(
&mut self,
content: &str,
attrs: &TextAttrs,
textures: &mut Textures,
) -> TextureHandle {
let metrics = Metrics::new(attrs.size, attrs.line_height);
let mut buffer = Buffer::new(&mut self.font_system, metrics);
let mut buffer = buffer.borrow_with(&mut self.font_system);
buffer.set_text(content, &Attrs::new(), Shaping::Advanced);
@@ -33,9 +55,10 @@ impl TextData {
let mut min_y = 0;
let mut max_x = 0;
let mut max_y = 0;
let c = attrs.color;
buffer.draw(
&mut self.cache,
cosmic_text::Color::rgb(0xff, 0xff, 0xff),
&mut self.swash_cache,
cosmic_text::Color::rgba(c.r, c.g, c.b, c.a),
|x, y, _, _, color| {
min_x = min_x.min(x);
min_y = min_y.min(y);

View File

@@ -1,18 +1,17 @@
use std::ops::Index;
use std::{
ops::Index,
sync::mpsc::{Receiver, Sender, channel},
};
use image::DynamicImage;
use crate::{
layout::{UiMsg, UiMsgSender},
render::TexturePrimitive,
util::RefCounter,
};
use crate::{render::TexturePrimitive, util::RefCounter};
#[derive(Clone)]
pub struct TextureHandle {
pub inner: TexturePrimitive,
counter: RefCounter,
send: UiMsgSender,
send: Sender<u32>,
}
/// a texture manager for a ui
@@ -21,7 +20,8 @@ pub struct Textures {
free: Vec<u32>,
images: Vec<Option<DynamicImage>>,
updates: Vec<Update>,
send: UiMsgSender,
send: Sender<u32>,
recv: Receiver<u32>,
}
pub enum TextureUpdate<'a> {
@@ -37,12 +37,14 @@ enum Update {
}
impl Textures {
pub fn new(send: UiMsgSender) -> Self {
pub fn new() -> Self {
let (send, recv) = channel();
Self {
free: Vec::new(),
images: Vec::new(),
updates: Vec::new(),
send,
recv,
}
}
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
@@ -72,10 +74,12 @@ impl Textures {
}
}
pub fn free(&mut self, idx: u32) {
self.images[idx as usize] = None;
self.updates.push(Update::Free(idx));
self.free.push(idx);
pub fn free(&mut self) {
for idx in self.recv.try_iter() {
self.images[idx as usize] = None;
self.updates.push(Update::Free(idx));
self.free.push(idx);
}
}
pub fn updates(&mut self) -> impl Iterator<Item = TextureUpdate<'_>> {
@@ -90,7 +94,7 @@ impl Textures {
impl Drop for TextureHandle {
fn drop(&mut self) {
if self.counter.drop() {
let _ = self.send.send(UiMsg::FreeTexture(self.inner.view_idx));
let _ = self.send.send(self.inner.view_idx);
}
}
}
@@ -102,3 +106,9 @@ impl Index<&TextureHandle> for Textures {
self.images[index.inner.view_idx as usize].as_ref().unwrap()
}
}
impl Default for Textures {
fn default() -> Self {
Self::new()
}
}

View File

@@ -19,8 +19,8 @@ pub struct Ui<Ctx> {
base: Option<WidgetId>,
widgets: Widgets<Ctx>,
updates: Vec<WidgetId>,
recv: Receiver<UiMsg>,
send: UiMsgSender,
recv: Receiver<Id>,
send: Sender<Id>,
size: Vec2,
// TODO: make these non pub(crate)
pub(crate) primitives: Primitives,
@@ -32,12 +32,6 @@ pub struct Ui<Ctx> {
pub(super) sensor_map: SensorMap<Ctx>,
}
pub enum UiMsg {
FreeWidget(Id),
FreeTexture(u32),
}
pub type UiMsgSender = Sender<UiMsg>;
#[derive(Default)]
pub struct Widgets<Ctx> {
ids: IdTracker,
@@ -96,7 +90,6 @@ impl<Ctx> Ui<Ctx> {
pub fn resize(&mut self, size: impl Into<Vec2>) {
self.size = size.into();
self.full_redraw = true;
}
pub fn redraw_all(&mut self, ctx: &mut Ctx)
@@ -105,6 +98,7 @@ impl<Ctx> Ui<Ctx> {
{
self.active_sensors.clear();
self.primitives.clear();
self.free();
let mut painter = Painter::new(
&self.widgets,
&mut self.primitives,
@@ -124,8 +118,6 @@ impl<Ctx> Ui<Ctx> {
where
Ctx: 'static,
{
self.recv_msgs();
if self.full_redraw {
self.redraw_all(ctx);
self.full_redraw = false;
@@ -136,13 +128,12 @@ impl<Ctx> Ui<Ctx> {
}
}
fn recv_msgs(&mut self) {
while let Ok(msg) = self.recv.try_recv() {
match msg {
UiMsg::FreeWidget(id) => self.widgets.delete(id),
UiMsg::FreeTexture(id) => self.textures.free(id),
}
/// free any resources that don't have references anymore
fn free(&mut self) {
for id in self.recv.try_iter() {
self.widgets.delete(id);
}
self.textures.free();
}
pub fn needs_redraw(&self) -> bool {
@@ -237,7 +228,7 @@ impl<Ctx: 'static> Default for Ui<Ctx> {
widgets: Widgets::new(),
updates: Default::default(),
primitives: Default::default(),
textures: Textures::new(send.clone()),
textures: Textures::new(),
text: TextData::default(),
full_redraw: false,
active_sensors: Default::default(),

View File

@@ -61,8 +61,8 @@ fn vs_main(
) -> VertexOutput {
var out: VertexOutput;
let top_left = in.top_left_anchor * window.dim + in.top_left_offset;
let bot_right = in.bottom_right_anchor * window.dim + in.bottom_right_offset;
let top_left = round(in.top_left_anchor * window.dim) + in.top_left_offset;
let bot_right = round(in.bottom_right_anchor * window.dim) + in.bottom_right_offset;
let size = bot_right - top_left;
let uv = vec2<f32>(

View File

@@ -23,9 +23,6 @@ impl GpuTextures {
TextureUpdate::Free(i) => self.free(i),
}
}
// if changed {
// println!("{}", self.views.len());
// }
changed
}
fn set(&mut self, i: u32, image: &DynamicImage) {

View File

@@ -98,7 +98,7 @@ impl Client {
.edit_on(Sense::HoverEnd, move |r, _| {
r.color = color;
});
(rect, text(label)).stack()
(rect, text(label).size(30)).stack()
}
let tabs = ui.add(