initial text impl
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use std::{any::TypeId, marker::PhantomData};
|
||||
|
||||
use crate::{
|
||||
FnTag, Ui, UiMsg, UiMsgSender, Widget, WidgetLike, WidgetTag,
|
||||
layout::{FnTag, Ui, UiMsg, UiMsgSender, Widget, WidgetLike, WidgetTag},
|
||||
util::{Id, RefCounter},
|
||||
};
|
||||
|
||||
@@ -83,10 +83,10 @@ pub struct IdTag;
|
||||
// pub trait WidgetIdFn<W, Ctx> = FnOnce(&mut Ui<Ctx>) -> WidgetId<W>;
|
||||
macro_rules! WidgetIdFnRet {
|
||||
($W:ty, $Ctx:ty) => {
|
||||
impl FnOnce(&mut $crate::Ui<$Ctx>) -> $crate::WidgetId<$W>
|
||||
impl FnOnce(&mut $crate::layout::Ui<$Ctx>) -> $crate::layout::WidgetId<$W>
|
||||
};
|
||||
($W:ty, $Ctx:ty, $($use:tt)*) => {
|
||||
impl FnOnce(&mut $crate::Ui<$Ctx>) -> $crate::WidgetId<$W> + use<$($use)*>
|
||||
impl FnOnce(&mut $crate::layout::Ui<$Ctx>) -> $crate::layout::WidgetId<$W> + use<$($use)*>
|
||||
};
|
||||
}
|
||||
pub(crate) use WidgetIdFnRet;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
mod color;
|
||||
mod id;
|
||||
mod num;
|
||||
mod orientation;
|
||||
mod painter;
|
||||
mod region;
|
||||
mod pos;
|
||||
mod sense;
|
||||
mod text;
|
||||
mod texture;
|
||||
mod ui;
|
||||
mod vec2;
|
||||
@@ -10,9 +13,12 @@ mod widget;
|
||||
|
||||
pub use color::*;
|
||||
pub use id::*;
|
||||
pub use num::*;
|
||||
pub use orientation::*;
|
||||
pub use painter::*;
|
||||
pub use region::*;
|
||||
pub use pos::*;
|
||||
pub use sense::*;
|
||||
pub use text::*;
|
||||
pub use texture::*;
|
||||
pub use ui::*;
|
||||
pub use vec2::*;
|
||||
|
||||
21
src/layout/num.rs
Normal file
21
src/layout/num.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
pub trait UiNum {
|
||||
fn to_f32(self) -> f32;
|
||||
}
|
||||
|
||||
impl UiNum for f32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl UiNum for u32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self as f32
|
||||
}
|
||||
}
|
||||
|
||||
impl UiNum for i32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self as f32
|
||||
}
|
||||
}
|
||||
49
src/layout/orientation.rs
Normal file
49
src/layout/orientation.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use crate::layout::{Vec2, vec2};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Corner {
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BotLeft,
|
||||
BotRight,
|
||||
}
|
||||
|
||||
impl Corner {
|
||||
pub const fn anchor(&self) -> Vec2 {
|
||||
match self {
|
||||
Corner::TopLeft => vec2(0.0, 0.0),
|
||||
Corner::TopRight => vec2(1.0, 0.0),
|
||||
Corner::BotLeft => vec2(0.0, 1.0),
|
||||
Corner::BotRight => vec2(1.0, 1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Axis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub struct Dir {
|
||||
pub axis: Axis,
|
||||
pub sign: Sign,
|
||||
}
|
||||
|
||||
impl Dir {
|
||||
pub const fn new(axis: Axis, dir: Sign) -> Self {
|
||||
Self { axis, sign: dir }
|
||||
}
|
||||
|
||||
pub const LEFT: Self = Self::new(Axis::X, Sign::Neg);
|
||||
pub const RIGHT: Self = Self::new(Axis::X, Sign::Pos);
|
||||
pub const UP: Self = Self::new(Axis::Y, Sign::Neg);
|
||||
pub const DOWN: Self = Self::new(Axis::Y, Sign::Pos);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Sign {
|
||||
Neg,
|
||||
Pos,
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
use image::GenericImageView;
|
||||
|
||||
use crate::{
|
||||
ActiveSensors, SensorMap, TextureHandle, UiRegion, WidgetId, Widgets,
|
||||
layout::{
|
||||
ActiveSensors, SensorMap, TextData, TextureHandle, Textures, UiPos, UiRegion, Vec2,
|
||||
WidgetId, Widgets,
|
||||
},
|
||||
render::{Primitive, Primitives},
|
||||
};
|
||||
|
||||
@@ -9,16 +14,22 @@ pub struct Painter<'a, Ctx: 'static> {
|
||||
sensors_map: &'a SensorMap<Ctx>,
|
||||
active_sensors: &'a mut ActiveSensors,
|
||||
primitives: &'a mut Primitives,
|
||||
textures: &'a mut Textures,
|
||||
text: &'a mut TextData,
|
||||
region: UiRegion,
|
||||
screen_size: Vec2,
|
||||
}
|
||||
|
||||
impl<'a, Ctx> Painter<'a, Ctx> {
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
nodes: &'a Widgets<Ctx>,
|
||||
primitives: &'a mut Primitives,
|
||||
ctx: &'a mut Ctx,
|
||||
sensors_map: &'a SensorMap<Ctx>,
|
||||
active_sensors: &'a mut ActiveSensors,
|
||||
text: &'a mut TextData,
|
||||
textures: &'a mut Textures,
|
||||
screen_size: Vec2,
|
||||
) -> Self {
|
||||
Self {
|
||||
nodes,
|
||||
@@ -26,7 +37,10 @@ impl<'a, Ctx> Painter<'a, Ctx> {
|
||||
active_sensors,
|
||||
sensors_map,
|
||||
primitives,
|
||||
text,
|
||||
region: UiRegion::full(),
|
||||
textures,
|
||||
screen_size,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,9 +83,30 @@ impl<'a, Ctx> Painter<'a, Ctx> {
|
||||
}
|
||||
|
||||
pub fn draw_texture(&mut self, handle: &TextureHandle) {
|
||||
self.primitives.drawn_textures.push(handle.clone());
|
||||
self.write(handle.inner);
|
||||
}
|
||||
|
||||
pub fn draw_texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
|
||||
let old = self.region;
|
||||
self.region = region;
|
||||
self.draw_texture(handle);
|
||||
self.region = old;
|
||||
}
|
||||
|
||||
pub fn draw_text(&mut self, content: &str) {
|
||||
let handle = self.text.draw(content, 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),
|
||||
};
|
||||
self.draw_texture_at(&handle, region);
|
||||
}
|
||||
|
||||
pub fn region(&self) -> UiRegion {
|
||||
self.region
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::{
|
||||
Axis, Vec2,
|
||||
layout::vec2,
|
||||
layout::{Axis, Corner, Vec2, vec2},
|
||||
util::{F32Util, impl_op},
|
||||
};
|
||||
|
||||
@@ -12,6 +11,15 @@ pub struct UiPos {
|
||||
}
|
||||
|
||||
impl UiPos {
|
||||
/// expands this position into a sized region centered at self
|
||||
pub fn expand(&self, size: impl Into<Vec2>) -> UiRegion {
|
||||
let size = size.into();
|
||||
UiRegion {
|
||||
top_left: self.shifted(-size / 2.0),
|
||||
bot_right: self.shifted(size / 2.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn center() -> Self {
|
||||
Self::anchor(vec2(0.5, 0.5))
|
||||
}
|
||||
@@ -23,6 +31,13 @@ impl UiPos {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn offset(offset: Vec2) -> Self {
|
||||
Self {
|
||||
anchor: Vec2::ZERO,
|
||||
offset,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn corner(corner: Corner) -> Self {
|
||||
Self::anchor(corner.anchor())
|
||||
}
|
||||
@@ -69,6 +84,15 @@ impl UiPos {
|
||||
self.flip();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_size(&self, size: Vec2) -> Vec2 {
|
||||
self.anchor * size + self.offset
|
||||
}
|
||||
|
||||
/// snaps this to a specific screen size to get actual pixel coordinates
|
||||
pub fn snap(&self, size: Vec2) -> Vec2 {
|
||||
self.to_size(size).round()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -122,21 +146,12 @@ impl UiRegion {
|
||||
bot_right: UiPos::corner(Corner::BotRight),
|
||||
}
|
||||
}
|
||||
pub fn center() -> Self {
|
||||
Self::anchor(Vec2::new(0.5, 0.5))
|
||||
}
|
||||
pub fn anchor(anchor: Vec2) -> Self {
|
||||
Self {
|
||||
top_left: UiPos::anchor(anchor),
|
||||
bot_right: UiPos::anchor(anchor),
|
||||
}
|
||||
}
|
||||
pub fn size(mut self, size: impl Into<Vec2>) -> Self {
|
||||
let size = size.into();
|
||||
self.top_left = self.top_left.shifted(-size / 2.0);
|
||||
self.bot_right = self.bot_right.shifted(size / 2.0);
|
||||
self
|
||||
}
|
||||
pub fn within(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
top_left: self.top_left.within(parent),
|
||||
@@ -170,19 +185,16 @@ impl UiRegion {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn corner(corner: Corner) -> Self {
|
||||
Self {
|
||||
top_left: UiPos::corner(corner),
|
||||
bot_right: UiPos::corner(corner),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_screen(&self, size: Vec2) -> ScreenRect {
|
||||
ScreenRect {
|
||||
top_left: self.top_left.anchor * size + self.top_left.offset,
|
||||
bot_right: self.bot_right.anchor * size + self.bot_right.offset,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn center(&self) -> UiPos {
|
||||
UiPos::center().within(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -221,22 +233,3 @@ impl UIScalarView<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Corner {
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BotLeft,
|
||||
BotRight,
|
||||
}
|
||||
|
||||
impl Corner {
|
||||
pub const fn anchor(&self) -> Vec2 {
|
||||
match self {
|
||||
Corner::TopLeft => vec2(0.0, 0.0),
|
||||
Corner::TopRight => vec2(1.0, 0.0),
|
||||
Corner::BotLeft => vec2(0.0, 1.0),
|
||||
Corner::BotRight => vec2(1.0, 1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::{HashMap, Ui, UiRegion, Vec2, WidgetId, util::Id};
|
||||
use crate::{
|
||||
HashMap,
|
||||
layout::{Ui, UiRegion, Vec2, WidgetId},
|
||||
util::Id,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Sense {
|
||||
@@ -81,10 +85,7 @@ impl<Ctx> Ui<Ctx> {
|
||||
|
||||
for sensor in &mut group.sensors {
|
||||
if should_run(sensor.sense, group.cursor, group.hover) {
|
||||
(sensor.f.box_clone())(SenseCtx {
|
||||
ui: self,
|
||||
app: ctx,
|
||||
});
|
||||
(sensor.f.box_clone())(SenseCtx { ui: self, app: ctx });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
57
src/layout/text.rs
Normal file
57
src/layout/text.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping, SwashCache};
|
||||
use image::{Rgba, RgbaImage};
|
||||
|
||||
use crate::{
|
||||
HashMap,
|
||||
layout::{TextureHandle, Textures},
|
||||
};
|
||||
|
||||
pub(crate) struct TextData {
|
||||
font_system: FontSystem,
|
||||
cache: SwashCache,
|
||||
}
|
||||
|
||||
impl Default for TextData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
font_system: FontSystem::new(),
|
||||
cache: SwashCache::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextData {
|
||||
pub fn draw(&mut self, content: &str, textures: &mut Textures) -> TextureHandle {
|
||||
let metrics = Metrics::new(30.0, 30.0);
|
||||
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);
|
||||
|
||||
// dawg what is this api ???
|
||||
let mut pixels = HashMap::new();
|
||||
let mut min_x = 0;
|
||||
let mut min_y = 0;
|
||||
let mut max_x = 0;
|
||||
let mut max_y = 0;
|
||||
buffer.draw(
|
||||
&mut self.cache,
|
||||
cosmic_text::Color::rgb(0xff, 0xff, 0xff),
|
||||
|x, y, _, _, color| {
|
||||
min_x = min_x.min(x);
|
||||
min_y = min_y.min(y);
|
||||
max_x = max_x.max(x);
|
||||
max_y = max_y.max(y);
|
||||
pixels.insert((x, y), Rgba(color.as_rgba()));
|
||||
},
|
||||
);
|
||||
let width = (max_x - min_x + 1) as u32;
|
||||
let height = (max_y - min_y + 1) as u32;
|
||||
let mut image = RgbaImage::new(width, height);
|
||||
for ((x, y), color) in pixels {
|
||||
let x = (x - min_x) as u32;
|
||||
let y = (y - min_y) as u32;
|
||||
image.put_pixel(x, y, color);
|
||||
}
|
||||
textures.add(image)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
use std::ops::Index;
|
||||
|
||||
use image::DynamicImage;
|
||||
|
||||
use crate::{UiMsg, UiMsgSender, render::TexturePrimitive, util::RefCounter};
|
||||
use crate::{
|
||||
layout::{UiMsg, UiMsgSender},
|
||||
render::TexturePrimitive,
|
||||
util::RefCounter,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TextureHandle {
|
||||
pub inner: TexturePrimitive,
|
||||
counter: RefCounter,
|
||||
@@ -10,11 +17,11 @@ pub struct TextureHandle {
|
||||
|
||||
/// a texture manager for a ui
|
||||
/// note that this is heavily oriented towards wgpu's renderer so the primitives don't need mapped
|
||||
#[derive(Default)]
|
||||
pub struct Textures {
|
||||
free: Vec<u32>,
|
||||
images: Vec<Option<DynamicImage>>,
|
||||
updates: Vec<Update>,
|
||||
send: UiMsgSender,
|
||||
}
|
||||
|
||||
pub enum TextureUpdate<'a> {
|
||||
@@ -30,8 +37,16 @@ enum Update {
|
||||
}
|
||||
|
||||
impl Textures {
|
||||
pub fn add(&mut self, image: DynamicImage, send: UiMsgSender) -> TextureHandle {
|
||||
let view_idx = self.push(image);
|
||||
pub fn new(send: UiMsgSender) -> Self {
|
||||
Self {
|
||||
free: Vec::new(),
|
||||
images: Vec::new(),
|
||||
updates: Vec::new(),
|
||||
send,
|
||||
}
|
||||
}
|
||||
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
|
||||
let view_idx = self.push(image.into());
|
||||
// 0 == default in renderer; TODO: actually create samplers here
|
||||
let sampler_idx = 0;
|
||||
TextureHandle {
|
||||
@@ -40,7 +55,7 @@ impl Textures {
|
||||
sampler_idx,
|
||||
},
|
||||
counter: RefCounter::new(),
|
||||
send,
|
||||
send: self.send.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,3 +94,11 @@ impl Drop for TextureHandle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<&TextureHandle> for Textures {
|
||||
type Output = DynamicImage;
|
||||
|
||||
fn index(&self, index: &TextureHandle) -> &Self::Output {
|
||||
self.images[index.inner.view_idx as usize].as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use image::DynamicImage;
|
||||
|
||||
use crate::{
|
||||
ActiveSensors, HashMap, Painter, SensorMap, TextureHandle, Textures, Widget, WidgetId,
|
||||
WidgetLike,
|
||||
HashMap,
|
||||
layout::{
|
||||
ActiveSensors, Painter, SensorMap, TextData, TextureHandle, Textures, Vec2, Widget,
|
||||
WidgetId, WidgetLike,
|
||||
},
|
||||
render::Primitives,
|
||||
util::{Id, IdTracker},
|
||||
};
|
||||
@@ -18,8 +21,11 @@ pub struct Ui<Ctx> {
|
||||
updates: Vec<WidgetId>,
|
||||
recv: Receiver<UiMsg>,
|
||||
send: UiMsgSender,
|
||||
size: Vec2,
|
||||
// TODO: make these non pub(crate)
|
||||
pub(crate) primitives: Primitives,
|
||||
pub(crate) textures: Textures,
|
||||
pub(crate) text: TextData,
|
||||
full_redraw: bool,
|
||||
|
||||
pub(super) active_sensors: ActiveSensors,
|
||||
@@ -85,7 +91,12 @@ impl<Ctx> Ui<Ctx> {
|
||||
}
|
||||
|
||||
pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle {
|
||||
self.textures.add(image, self.send.clone())
|
||||
self.textures.add(image)
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -100,6 +111,9 @@ impl<Ctx> Ui<Ctx> {
|
||||
ctx,
|
||||
&self.sensor_map,
|
||||
&mut self.active_sensors,
|
||||
&mut self.text,
|
||||
&mut self.textures,
|
||||
self.size,
|
||||
);
|
||||
if let Some(base) = &self.base {
|
||||
painter.draw(base);
|
||||
@@ -217,18 +231,20 @@ impl<Ctx> dyn Widget<Ctx> {
|
||||
|
||||
impl<Ctx: 'static> Default for Ui<Ctx> {
|
||||
fn default() -> Self {
|
||||
let (del_send, del_recv) = channel();
|
||||
let (send, recv) = channel();
|
||||
Self {
|
||||
base: Default::default(),
|
||||
widgets: Widgets::new(),
|
||||
updates: Default::default(),
|
||||
primitives: Default::default(),
|
||||
textures: Textures::default(),
|
||||
textures: Textures::new(send.clone()),
|
||||
text: TextData::default(),
|
||||
full_redraw: false,
|
||||
active_sensors: Default::default(),
|
||||
sensor_map: Default::default(),
|
||||
send: del_send,
|
||||
recv: del_recv,
|
||||
send,
|
||||
recv,
|
||||
size: Vec2::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::{util::{impl_op, F32Util}, UiNum};
|
||||
use crate::{
|
||||
layout::UiNum,
|
||||
util::{F32Util, impl_op},
|
||||
};
|
||||
use std::ops::*;
|
||||
|
||||
#[repr(C)]
|
||||
@@ -28,6 +31,27 @@ impl Vec2 {
|
||||
y: self.y.lerp(from.y, to.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn round(self) -> Self {
|
||||
Self {
|
||||
x: self.x.round(),
|
||||
y: self.y.round(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn floor(self) -> Self {
|
||||
Self {
|
||||
x: self.x.floor(),
|
||||
y: self.y.floor(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn ceil(self) -> Self {
|
||||
Self {
|
||||
x: self.x.ceil(),
|
||||
y: self.y.ceil(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl const From<f32> for Vec2 {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{Painter, Ui, WidgetId, WidgetIdFnRet};
|
||||
use crate::layout::{Painter, Ui, WidgetId, WidgetIdFnRet};
|
||||
|
||||
use std::{any::Any, marker::PhantomData};
|
||||
|
||||
pub trait Widget<Ctx>: Any {
|
||||
@@ -33,7 +34,7 @@ pub trait WidgetLike<Ctx, Tag> {
|
||||
/// currently a macro for rust analyzer (doesn't support trait aliases atm)
|
||||
macro_rules! WidgetFnRet {
|
||||
($W:ty, $Ctx:ty) => {
|
||||
impl FnOnce(&mut $crate::Ui<$Ctx>) -> $W
|
||||
impl FnOnce(&mut $crate::layout::Ui<$Ctx>) -> $W
|
||||
};
|
||||
}
|
||||
pub(crate) use WidgetFnRet;
|
||||
|
||||
Reference in New Issue
Block a user