sized widgets!
This commit is contained in:
@@ -6,7 +6,7 @@ pub struct Regioned {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Regioned {
|
impl Widget for Regioned {
|
||||||
fn draw(&self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.draw_within(&self.inner, self.region);
|
painter.draw_within(&self.inner, self.region);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,34 +2,23 @@ use crate::prelude::*;
|
|||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
|
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
handle: Option<TextureHandle>,
|
handle: TextureHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Image {
|
impl Widget for Image {
|
||||||
fn draw(&self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
if let Some(handle) = &self.handle {
|
painter.draw_texture(&self.handle);
|
||||||
painter.draw_texture(handle);
|
}
|
||||||
} else {
|
|
||||||
painter.write(RectPrimitive {
|
fn size(&mut self, _: SizeCtx) -> Vec2 {
|
||||||
color: Color::MAGENTA,
|
self.handle.size()
|
||||||
inner_radius: 0.0,
|
|
||||||
radius: 0.0,
|
|
||||||
thickness: 0.0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image(image: impl LoadableImage) -> WidgetFnRet!(Image) {
|
pub fn image(image: impl LoadableImage) -> WidgetFnRet!(Image) {
|
||||||
let image = match image.get_image() {
|
let image = image.get_image().expect("Failed to load image");
|
||||||
Ok(image) => Some(image),
|
|
||||||
Err(e) => {
|
|
||||||
println!("Failed to load image: {e}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
move |ui| Image {
|
move |ui| Image {
|
||||||
handle: image.map(|image| ui.add_texture(image)),
|
handle: ui.add_texture(image),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ impl Rect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Rect {
|
impl Widget for Rect {
|
||||||
fn draw(&self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.write(RectPrimitive {
|
painter.write(RectPrimitive {
|
||||||
color: self.color,
|
color: self.color,
|
||||||
radius: self.radius,
|
radius: self.radius,
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ pub struct Span {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Span {
|
impl Widget for Span {
|
||||||
fn draw(&self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let total = self.sums();
|
let total = self.setup(painter);
|
||||||
let mut start = UIScalar::min();
|
let mut start = UIScalar::min();
|
||||||
for (child, length) in &self.children {
|
for (child, length) in &self.children {
|
||||||
let mut child_region = UiRegion::full();
|
let mut child_region = UiRegion::full();
|
||||||
@@ -29,6 +29,14 @@ impl Widget for Span {
|
|||||||
start.anchor += rel;
|
start.anchor += rel;
|
||||||
axis.bot_right.set(start);
|
axis.bot_right.set(start);
|
||||||
}
|
}
|
||||||
|
SpanLen::Sized(size) => {
|
||||||
|
let offset = size.axis(self.dir.axis);
|
||||||
|
start.offset += offset;
|
||||||
|
*axis.bot_right.offset = start.offset;
|
||||||
|
child_region.bot_right.anchor = child_region.top_left.anchor;
|
||||||
|
let opposite = !self.dir.axis;
|
||||||
|
*child_region.bot_right.offset.axis_mut(opposite) += size.axis(opposite);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if self.dir.sign == Sign::Neg {
|
if self.dir.sign == Sign::Neg {
|
||||||
child_region.flip();
|
child_region.flip();
|
||||||
@@ -53,18 +61,23 @@ impl Span {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sums(&self) -> SpanLenSums {
|
fn setup(&mut self, painter: &mut Painter) -> SpanLenSums {
|
||||||
self.lengths().fold(SpanLenSums::default(), |mut s, l| {
|
self.children
|
||||||
match l {
|
.iter_mut()
|
||||||
SpanLen::Fixed(v) => s.fixed += v,
|
.fold(SpanLenSums::default(), |mut s, (id, l)| {
|
||||||
SpanLen::Ratio(v) => s.ratio += v,
|
match l {
|
||||||
SpanLen::Relative(v) => s.relative += v,
|
SpanLen::Fixed(v) => s.fixed += *v,
|
||||||
}
|
SpanLen::Ratio(v) => s.ratio += *v,
|
||||||
s
|
SpanLen::Relative(v) => s.relative += *v,
|
||||||
})
|
SpanLen::Sized(v) => {
|
||||||
}
|
let size = painter.size(id);
|
||||||
pub fn lengths(&self) -> impl ExactSizeIterator<Item = &SpanLen> {
|
let len = size.axis(self.dir.axis);
|
||||||
self.children.iter().map(|(_, s)| s)
|
*v = size;
|
||||||
|
s.fixed += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +91,10 @@ pub enum SpanLen {
|
|||||||
/// relative to the total space (of the entire span)
|
/// relative to the total space (of the entire span)
|
||||||
/// eg. 0.5 means 1/2 of the total space
|
/// eg. 0.5 means 1/2 of the total space
|
||||||
Relative(f32),
|
Relative(f32),
|
||||||
|
/// size determined by the child widget itself
|
||||||
|
/// the value is not used externally, I just don't wanna make a duplicate enum
|
||||||
|
/// there are util functions instead so
|
||||||
|
Sized(Vec2),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fixed<N: UiNum>(x: N) -> SpanLen {
|
pub fn fixed<N: UiNum>(x: N) -> SpanLen {
|
||||||
@@ -92,6 +109,10 @@ pub fn relative<N: UiNum>(x: N) -> SpanLen {
|
|||||||
SpanLen::Relative(x.to_f32())
|
SpanLen::Relative(x.to_f32())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sized() -> SpanLen {
|
||||||
|
SpanLen::Sized(Vec2::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
impl<N: UiNum> From<N> for SpanLen {
|
impl<N: UiNum> From<N> for SpanLen {
|
||||||
fn from(value: N) -> Self {
|
fn from(value: N) -> Self {
|
||||||
Self::Ratio(value.to_f32())
|
Self::Ratio(value.to_f32())
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ pub struct Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Stack {
|
impl Widget for Stack {
|
||||||
fn draw(&self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
for child in &self.children {
|
for child in &self.children {
|
||||||
painter.draw(child);
|
painter.draw(child);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
|
use cosmic_text::Metrics;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct Text {
|
pub struct Text {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub attrs: TextAttrs,
|
pub attrs: TextAttrs,
|
||||||
|
buf: TextBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Text {
|
impl Text {
|
||||||
pub fn size(mut self, size: impl UiNum) -> Self {
|
pub fn size(mut self, size: impl UiNum) -> Self {
|
||||||
self.attrs.size = size.to_f32();
|
self.attrs.size = size.to_f32();
|
||||||
|
self.attrs.line_height = self.attrs.size * 1.1;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn color(mut self, color: UiColor) -> Self {
|
pub fn color(mut self, color: UiColor) -> Self {
|
||||||
@@ -21,16 +25,31 @@ impl Text {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Text {
|
impl Widget for Text {
|
||||||
fn draw(&self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
|
let (handle, offset) = painter.render_text(&mut self.buf, &self.content, &self.attrs);
|
||||||
|
let dims = handle.size();
|
||||||
|
let size = offset.size(&handle);
|
||||||
|
let mut region = painter.region().center().expand(size);
|
||||||
|
region.top_left.offset += offset.top_left;
|
||||||
|
region.bot_right.offset = region.top_left.offset + dims;
|
||||||
|
painter.draw_texture_at(&handle, region);
|
||||||
// TODO: when on_update is added to painter,
|
// TODO: when on_update is added to painter,
|
||||||
// return & store TextureHandle to reuse
|
// reuse TextureHandle
|
||||||
painter.draw_text(&self.content, &self.attrs);
|
}
|
||||||
|
|
||||||
|
fn size(&mut self, ctx: SizeCtx) -> Vec2 {
|
||||||
|
let (handle, offset) =
|
||||||
|
ctx.text
|
||||||
|
.draw(&mut self.buf, &self.content, &self.attrs, ctx.textures);
|
||||||
|
offset.size(&handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text(text: impl Into<String>) -> Text {
|
pub fn text(text: impl Into<String>) -> Text {
|
||||||
|
let attrs = TextAttrs::default();
|
||||||
Text {
|
Text {
|
||||||
content: text.into(),
|
content: text.into(),
|
||||||
attrs: TextAttrs::default(),
|
buf: TextBuffer::new_empty(Metrics::new(attrs.size, attrs.line_height)),
|
||||||
|
attrs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::ops::Not;
|
||||||
|
|
||||||
use crate::layout::{Vec2, vec2};
|
use crate::layout::{Vec2, vec2};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@@ -25,6 +27,17 @@ pub enum Axis {
|
|||||||
Y,
|
Y,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Not for Axis {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn not(self) -> Self::Output {
|
||||||
|
match self {
|
||||||
|
Self::X => Self::Y,
|
||||||
|
Self::Y => Self::X,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
pub struct Dir {
|
pub struct Dir {
|
||||||
pub axis: Axis,
|
pub axis: Axis,
|
||||||
@@ -47,3 +60,19 @@ pub enum Sign {
|
|||||||
Neg,
|
Neg,
|
||||||
Pos,
|
Pos,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Vec2 {
|
||||||
|
pub fn axis(&self, axis: Axis) -> f32 {
|
||||||
|
match axis {
|
||||||
|
Axis::X => self.x,
|
||||||
|
Axis::Y => self.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn axis_mut(&mut self, axis: Axis) -> &mut f32 {
|
||||||
|
match axis {
|
||||||
|
Axis::X => &mut self.x,
|
||||||
|
Axis::Y => &mut self.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use image::GenericImageView;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
layout::{
|
layout::{
|
||||||
Active, SensorMap, TextAttrs, TextData, TextureHandle, Textures, UiRegion, Vec2, WidgetId,
|
Active, SensorMap, SizeCtx, TextAttrs, TextBuffer, TextData, TextOffset, TextureHandle,
|
||||||
WidgetInstance, Widgets,
|
Textures, UiRegion, Vec2, WidgetId, WidgetInstance, Widgets,
|
||||||
},
|
},
|
||||||
render::{Primitive, PrimitiveHandle, Primitives},
|
render::{Primitive, PrimitiveHandle, Primitives},
|
||||||
util::Id,
|
util::Id,
|
||||||
@@ -33,7 +31,7 @@ pub struct Painter<'a> {
|
|||||||
impl<'a> Painter<'a> {
|
impl<'a> Painter<'a> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
nodes: &'a Widgets,
|
widgets: &'a Widgets,
|
||||||
primitives: &'a mut Primitives,
|
primitives: &'a mut Primitives,
|
||||||
sensors_map: &'a SensorMap,
|
sensors_map: &'a SensorMap,
|
||||||
active: &'a mut Active,
|
active: &'a mut Active,
|
||||||
@@ -42,7 +40,7 @@ impl<'a> Painter<'a> {
|
|||||||
screen_size: Vec2,
|
screen_size: Vec2,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
widgets: nodes,
|
widgets,
|
||||||
active,
|
active,
|
||||||
sensors_map,
|
sensors_map,
|
||||||
primitives,
|
primitives,
|
||||||
@@ -59,9 +57,14 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes a primitive to be rendered
|
||||||
|
pub fn write_at<P: Primitive>(&mut self, data: P, region: UiRegion) -> PrimitiveHandle<P> {
|
||||||
|
self.primitives.write(data, region)
|
||||||
|
}
|
||||||
|
|
||||||
/// Writes a primitive to be rendered
|
/// Writes a primitive to be rendered
|
||||||
pub fn write<P: Primitive>(&mut self, data: P) -> PrimitiveHandle<P> {
|
pub fn write<P: Primitive>(&mut self, data: P) -> PrimitiveHandle<P> {
|
||||||
self.primitives.write(data, self.state.region)
|
self.write_at(data, self.state.region)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a widget within this widget's region.
|
/// Draws a widget within this widget's region.
|
||||||
@@ -81,9 +84,6 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_raw_at(&mut self, id: &Id, region: UiRegion) {
|
fn draw_raw_at(&mut self, id: &Id, region: UiRegion) {
|
||||||
if self.active.widgets.contains_key(id) {
|
|
||||||
panic!("widget drawn twice!");
|
|
||||||
}
|
|
||||||
self.state.children.push(id.duplicate());
|
self.state.children.push(id.duplicate());
|
||||||
|
|
||||||
// &mut self is passed to avoid copying all of the "static" pointers in self
|
// &mut self is passed to avoid copying all of the "static" pointers in self
|
||||||
@@ -100,7 +100,7 @@ impl<'a> Painter<'a> {
|
|||||||
|
|
||||||
// draw widgets
|
// draw widgets
|
||||||
let start_i = self.primitives.cur_pos();
|
let start_i = self.primitives.cur_pos();
|
||||||
self.widgets.get_dyn(id).draw(self);
|
self.widgets.get_dyn_dynamic(id).draw(self);
|
||||||
let end_i = self.primitives.cur_pos();
|
let end_i = self.primitives.cur_pos();
|
||||||
|
|
||||||
// restore state
|
// restore state
|
||||||
@@ -122,7 +122,7 @@ impl<'a> Painter<'a> {
|
|||||||
|
|
||||||
pub fn draw_texture(&mut self, handle: &TextureHandle) {
|
pub fn draw_texture(&mut self, handle: &TextureHandle) {
|
||||||
self.state.textures.push(handle.clone());
|
self.state.textures.push(handle.clone());
|
||||||
self.write(handle.inner);
|
self.write(handle.primitive());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
|
pub fn draw_texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
|
||||||
@@ -132,11 +132,14 @@ impl<'a> Painter<'a> {
|
|||||||
self.state.region = old;
|
self.state.region = old;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_text(&mut self, content: &str, attrs: &TextAttrs) {
|
/// returns (handle, offset from top left)
|
||||||
let handle = self.text.draw(content, attrs, self.textures);
|
pub fn render_text(
|
||||||
let dims: Vec2 = self.textures[&handle].dimensions().into();
|
&mut self,
|
||||||
let region = self.state.region.center().expand(dims);
|
buffer: &mut TextBuffer,
|
||||||
self.draw_texture_at(&handle, region);
|
content: &str,
|
||||||
|
attrs: &TextAttrs,
|
||||||
|
) -> (TextureHandle, TextOffset) {
|
||||||
|
self.text.draw(buffer, content, attrs, self.textures)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn region(&self) -> UiRegion {
|
pub fn region(&self) -> UiRegion {
|
||||||
@@ -147,6 +150,14 @@ impl<'a> Painter<'a> {
|
|||||||
self.state.region.in_size(self.screen_size)
|
self.state.region.in_size(self.screen_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn size<W>(&mut self, id: &WidgetId<W>) -> Vec2 {
|
||||||
|
self.widgets.get_dyn_dynamic(&id.id).size(SizeCtx {
|
||||||
|
size: self.region().in_size(self.screen_size),
|
||||||
|
text: self.text,
|
||||||
|
textures: self.textures,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn redraw(&mut self, id: &Id) {
|
pub(crate) fn redraw(&mut self, id: &Id) {
|
||||||
if !self.active.widgets.contains_key(id) {
|
if !self.active.widgets.contains_key(id) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping, SwashCache};
|
|||||||
use image::{Rgba, RgbaImage};
|
use image::{Rgba, RgbaImage};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
layout::{TextureHandle, Textures, UiColor},
|
layout::{TextureHandle, Textures, UiColor, Vec2},
|
||||||
util::HashMap,
|
util::HashMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,12 +20,15 @@ impl Default for TextData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub struct TextAttrs {
|
pub struct TextAttrs {
|
||||||
pub color: UiColor,
|
pub color: UiColor,
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
pub line_height: f32,
|
pub line_height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type TextBuffer = Buffer;
|
||||||
|
|
||||||
impl Default for TextAttrs {
|
impl Default for TextAttrs {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let size = 14.0;
|
let size = 14.0;
|
||||||
@@ -38,35 +41,57 @@ impl Default for TextAttrs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TextData {
|
impl TextData {
|
||||||
|
/// returns (handle, offset from top left)
|
||||||
pub fn draw(
|
pub fn draw(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
buffer: &mut TextBuffer,
|
||||||
content: &str,
|
content: &str,
|
||||||
attrs: &TextAttrs,
|
attrs: &TextAttrs,
|
||||||
textures: &mut Textures,
|
textures: &mut Textures,
|
||||||
) -> TextureHandle {
|
) -> (TextureHandle, TextOffset) {
|
||||||
let metrics = Metrics::new(attrs.size, attrs.line_height);
|
buffer.set_metrics(
|
||||||
let mut buffer = Buffer::new(&mut self.font_system, metrics);
|
&mut self.font_system,
|
||||||
let mut buffer = buffer.borrow_with(&mut self.font_system);
|
Metrics::new(attrs.size, attrs.line_height),
|
||||||
buffer.set_text(content, &Attrs::new(), Shaping::Advanced);
|
);
|
||||||
|
buffer.set_text(
|
||||||
// dawg what is this api ???
|
&mut self.font_system,
|
||||||
|
content,
|
||||||
|
&Attrs::new(),
|
||||||
|
Shaping::Advanced,
|
||||||
|
);
|
||||||
let mut pixels = HashMap::new();
|
let mut pixels = HashMap::new();
|
||||||
let mut min_x = 0;
|
let mut min_x = 0;
|
||||||
let mut min_y = 0;
|
let mut min_y = 0;
|
||||||
let mut max_x = 0;
|
let mut max_x = 0;
|
||||||
let mut max_y = 0;
|
let mut max_y = 0;
|
||||||
let c = attrs.color;
|
let c = attrs.color;
|
||||||
buffer.draw(
|
let mut max_width = 0.0f32;
|
||||||
&mut self.swash_cache,
|
for run in buffer.layout_runs() {
|
||||||
cosmic_text::Color::rgba(c.r, c.g, c.b, c.a),
|
for glyph in run.glyphs.iter() {
|
||||||
|x, y, _, _, color| {
|
let physical_glyph = glyph.physical((0., 0.), 1.0);
|
||||||
min_x = min_x.min(x);
|
|
||||||
min_y = min_y.min(y);
|
let glyph_color = match glyph.color_opt {
|
||||||
max_x = max_x.max(x);
|
Some(some) => some,
|
||||||
max_y = max_y.max(y);
|
None => cosmic_text::Color::rgba(c.r, c.g, c.b, c.a),
|
||||||
pixels.insert((x, y), Rgba(color.as_rgba()));
|
};
|
||||||
},
|
|
||||||
);
|
self.swash_cache.with_pixels(
|
||||||
|
&mut self.font_system,
|
||||||
|
physical_glyph.cache_key,
|
||||||
|
glyph_color,
|
||||||
|
|x, y, color| {
|
||||||
|
let x = physical_glyph.x + x;
|
||||||
|
let y = run.line_y as i32 + physical_glyph.y + y;
|
||||||
|
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()));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
max_width = max_width.max(run.line_w);
|
||||||
|
}
|
||||||
let width = (max_x - min_x + 1) as u32;
|
let width = (max_x - min_x + 1) as u32;
|
||||||
let height = (max_y - min_y + 1) as u32;
|
let height = (max_y - min_y + 1) as u32;
|
||||||
let mut image = RgbaImage::new(width, height);
|
let mut image = RgbaImage::new(width, height);
|
||||||
@@ -75,6 +100,25 @@ impl TextData {
|
|||||||
let y = (y - min_y) as u32;
|
let y = (y - min_y) as u32;
|
||||||
image.put_pixel(x, y, color);
|
image.put_pixel(x, y, color);
|
||||||
}
|
}
|
||||||
textures.add(image)
|
let offset = TextOffset {
|
||||||
|
top_left: Vec2::new(min_x as f32, min_y as f32),
|
||||||
|
bot_right: Vec2::new(
|
||||||
|
max_width - max_x as f32,
|
||||||
|
attrs.line_height * buffer.lines.len() as f32 - max_y as f32,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
(textures.add(image), offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct TextOffset {
|
||||||
|
pub top_left: Vec2,
|
||||||
|
pub bot_right: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextOffset {
|
||||||
|
pub fn size(&self, handle: &TextureHandle) -> Vec2 {
|
||||||
|
handle.size() - self.top_left + self.bot_right
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ use std::{
|
|||||||
sync::mpsc::{Receiver, Sender, channel},
|
sync::mpsc::{Receiver, Sender, channel},
|
||||||
};
|
};
|
||||||
|
|
||||||
use image::DynamicImage;
|
use image::{DynamicImage, GenericImageView};
|
||||||
|
|
||||||
use crate::{render::TexturePrimitive, util::RefCounter};
|
use crate::{layout::Vec2, render::TexturePrimitive, util::RefCounter};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TextureHandle {
|
pub struct TextureHandle {
|
||||||
pub inner: TexturePrimitive,
|
inner: TexturePrimitive,
|
||||||
|
size: Vec2,
|
||||||
counter: RefCounter,
|
counter: RefCounter,
|
||||||
send: Sender<u32>,
|
send: Sender<u32>,
|
||||||
}
|
}
|
||||||
@@ -25,10 +26,11 @@ pub struct Textures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum TextureUpdate<'a> {
|
pub enum TextureUpdate<'a> {
|
||||||
PushFree,
|
|
||||||
Push(&'a DynamicImage),
|
Push(&'a DynamicImage),
|
||||||
Set(u32, &'a DynamicImage),
|
Set(u32, &'a DynamicImage),
|
||||||
Free(u32),
|
Free(u32),
|
||||||
|
PushFree,
|
||||||
|
SetFree,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Update {
|
enum Update {
|
||||||
@@ -49,7 +51,9 @@ impl Textures {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
|
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
|
||||||
let view_idx = self.push(image.into());
|
let image = image.into();
|
||||||
|
let size = image.dimensions().into();
|
||||||
|
let view_idx = self.push(image);
|
||||||
// 0 == default in renderer; TODO: actually create samplers here
|
// 0 == default in renderer; TODO: actually create samplers here
|
||||||
let sampler_idx = 0;
|
let sampler_idx = 0;
|
||||||
TextureHandle {
|
TextureHandle {
|
||||||
@@ -57,6 +61,7 @@ impl Textures {
|
|||||||
view_idx,
|
view_idx,
|
||||||
sampler_idx,
|
sampler_idx,
|
||||||
},
|
},
|
||||||
|
size,
|
||||||
counter: RefCounter::new(),
|
counter: RefCounter::new(),
|
||||||
send: self.send.clone(),
|
send: self.send.clone(),
|
||||||
}
|
}
|
||||||
@@ -89,12 +94,24 @@ impl Textures {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(TextureUpdate::Push)
|
.map(TextureUpdate::Push)
|
||||||
.unwrap_or(TextureUpdate::PushFree),
|
.unwrap_or(TextureUpdate::PushFree),
|
||||||
Update::Set(i) => TextureUpdate::Set(i, self.images[i as usize].as_ref().unwrap()),
|
Update::Set(i) => self.images[i as usize]
|
||||||
|
.as_ref()
|
||||||
|
.map(|img| TextureUpdate::Set(i, img))
|
||||||
|
.unwrap_or(TextureUpdate::SetFree),
|
||||||
Update::Free(i) => TextureUpdate::Free(i),
|
Update::Free(i) => TextureUpdate::Free(i),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TextureHandle {
|
||||||
|
pub fn primitive(&self) -> TexturePrimitive {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
pub fn size(&self) -> Vec2 {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Drop for TextureHandle {
|
impl Drop for TextureHandle {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.counter.drop() {
|
if self.counter.drop() {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
ops::{Index, IndexMut, Range},
|
ops::{Deref, DerefMut, Index, IndexMut, Range},
|
||||||
sync::mpsc::{Receiver, Sender, channel},
|
sync::mpsc::{Receiver, Sender, channel},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,7 +35,12 @@ pub struct Ui {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Widgets {
|
pub struct Widgets {
|
||||||
ids: IdTracker,
|
ids: IdTracker,
|
||||||
map: HashMap<Id, Box<dyn Widget>>,
|
map: HashMap<Id, WidgetData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WidgetData {
|
||||||
|
widget: Box<dyn Widget>,
|
||||||
|
borrowed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ui {
|
impl Ui {
|
||||||
@@ -212,40 +217,57 @@ impl Widgets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_dyn(&self, id: &Id) -> &dyn Widget {
|
pub fn get_dyn(&self, id: &Id) -> Option<&dyn Widget> {
|
||||||
self.map.get(id).unwrap().as_ref()
|
Some(self.map.get(id)?.widget.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_dyn_mut(&mut self, id: &Id) -> Option<&mut dyn Widget> {
|
||||||
|
Some(self.map.get_mut(id)?.widget.as_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get_dyn but dynamic borrow checking of widgets
|
||||||
|
/// lets you do recursive (tree) operations, like the painter does
|
||||||
|
pub fn get_dyn_dynamic(&self, id: &Id) -> WidgetWrapper<'_> {
|
||||||
|
// must guarantee no other mutable references to this widget exist
|
||||||
|
// done through the borrow variable
|
||||||
|
let data: &mut WidgetData = unsafe { std::mem::transmute(self.map.get(id)) };
|
||||||
|
if data.borrowed {
|
||||||
|
panic!("tried to mutably borrow the same widget twice");
|
||||||
|
}
|
||||||
|
WidgetWrapper {
|
||||||
|
widget: data.widget.as_mut(),
|
||||||
|
borrowed: &mut data.borrowed,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_static<W: Widget>(&self, id: &StaticWidgetId<W>) -> Option<&W> {
|
pub fn get_static<W: Widget>(&self, id: &StaticWidgetId<W>) -> Option<&W> {
|
||||||
self.map.get(&id.id.id()).unwrap().as_any().downcast_ref()
|
self.get_dyn(&id.id.id())?.as_any().downcast_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_static_mut<W: Widget>(&mut self, id: &StaticWidgetId<W>) -> Option<&mut W> {
|
pub fn get_static_mut<W: Widget>(&mut self, id: &StaticWidgetId<W>) -> Option<&mut W> {
|
||||||
self.map
|
self.get_dyn_mut(&id.id.id())?.as_any_mut().downcast_mut()
|
||||||
.get_mut(&id.id.id())
|
|
||||||
.unwrap()
|
|
||||||
.as_any_mut()
|
|
||||||
.downcast_mut()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get<W: Widget>(&self, id: &WidgetId<W>) -> Option<&W> {
|
pub fn get<W: Widget>(&self, id: &WidgetId<W>) -> Option<&W> {
|
||||||
self.map.get(&id.id).unwrap().as_any().downcast_ref()
|
self.get_dyn(&id.id)?.as_any().downcast_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut<W: Widget>(&mut self, id: &WidgetId<W>) -> Option<&mut W> {
|
pub fn get_mut<W: Widget>(&mut self, id: &WidgetId<W>) -> Option<&mut W> {
|
||||||
self.map
|
self.get_dyn_mut(&id.id)?.as_any_mut().downcast_mut()
|
||||||
.get_mut(&id.id)
|
|
||||||
.unwrap()
|
|
||||||
.as_any_mut()
|
|
||||||
.downcast_mut()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, id: Id, widget: impl Widget) {
|
pub fn insert(&mut self, id: Id, widget: impl Widget) {
|
||||||
self.map.insert(id, Box::new(widget));
|
self.insert_any(id, Box::new(widget));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_any(&mut self, id: Id, widget: Box<dyn Widget>) {
|
pub fn insert_any(&mut self, id: Id, widget: Box<dyn Widget>) {
|
||||||
self.map.insert(id, widget);
|
self.map.insert(
|
||||||
|
id,
|
||||||
|
WidgetData {
|
||||||
|
widget,
|
||||||
|
borrowed: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&mut self, id: Id) {
|
pub fn delete(&mut self, id: Id) {
|
||||||
@@ -266,6 +288,37 @@ impl Widgets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct WidgetWrapper<'a> {
|
||||||
|
widget: &'a mut dyn Widget,
|
||||||
|
borrowed: &'a mut bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> WidgetWrapper<'a> {
|
||||||
|
pub fn new(widget: &'a mut dyn Widget, borrowed: &'a mut bool) -> Self {
|
||||||
|
Self { widget, borrowed }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for WidgetWrapper<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
*self.borrowed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for WidgetWrapper<'_> {
|
||||||
|
type Target = dyn Widget;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.widget
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for WidgetWrapper<'_> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.widget
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl dyn Widget {
|
impl dyn Widget {
|
||||||
pub fn as_any(&self) -> &dyn Any {
|
pub fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
use crate::layout::{Painter, Ui, WidgetId, WidgetIdFnRet};
|
use crate::layout::{Painter, TextData, Textures, Ui, Vec2, WidgetId, WidgetIdFnRet};
|
||||||
|
|
||||||
use std::{any::Any, marker::PhantomData};
|
use std::{any::Any, marker::PhantomData};
|
||||||
|
|
||||||
pub trait Widget: Any {
|
pub trait Widget: Any {
|
||||||
fn draw(&self, painter: &mut Painter);
|
fn draw(&mut self, painter: &mut Painter);
|
||||||
|
fn size(&mut self, ctx: SizeCtx) -> Vec2 {
|
||||||
|
ctx.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SizeCtx<'a> {
|
||||||
|
pub size: Vec2,
|
||||||
|
pub text: &'a mut TextData,
|
||||||
|
pub textures: &'a mut Textures,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WidgetTag;
|
pub struct WidgetTag;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ impl GpuTextures {
|
|||||||
match update {
|
match update {
|
||||||
TextureUpdate::Push(image) => self.push(image),
|
TextureUpdate::Push(image) => self.push(image),
|
||||||
TextureUpdate::Set(i, image) => self.set(i, image),
|
TextureUpdate::Set(i, image) => self.set(i, image),
|
||||||
|
TextureUpdate::SetFree => self.view_count += 1,
|
||||||
TextureUpdate::Free(i) => self.free(i),
|
TextureUpdate::Free(i) => self.free(i),
|
||||||
TextureUpdate::PushFree => self.push_free(),
|
TextureUpdate::PushFree => self.push_free(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,11 +88,25 @@ impl Client {
|
|||||||
(rect, text(label).size(30)).stack()
|
(rect, text(label).size(30)).stack()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let text_test = ui.add_static(
|
||||||
|
(
|
||||||
|
text("this is a").size(30),
|
||||||
|
text("teeeeeeeest").size(30),
|
||||||
|
text("okkk\nokkkkkk!").size(30),
|
||||||
|
text("hmm").size(30),
|
||||||
|
text("a").size(30),
|
||||||
|
text("'").size(30),
|
||||||
|
text("pretty cool right?").size(30),
|
||||||
|
)
|
||||||
|
.span(Dir::RIGHT, [sized(); _]),
|
||||||
|
);
|
||||||
|
|
||||||
let tabs = ui.add(
|
let tabs = ui.add(
|
||||||
(
|
(
|
||||||
switch_button(Color::RED, pad_test, "pad test"),
|
switch_button(Color::RED, pad_test, "pad test"),
|
||||||
switch_button(Color::GREEN, span_test, "span test"),
|
switch_button(Color::GREEN, span_test, "span test"),
|
||||||
switch_button(Color::BLUE, span_add, "span add test"),
|
switch_button(Color::BLUE, span_add, "span add test"),
|
||||||
|
switch_button(Color::MAGENTA, text_test, "text test"),
|
||||||
)
|
)
|
||||||
.span(Dir::RIGHT, [1; _]),
|
.span(Dir::RIGHT, [1; _]),
|
||||||
);
|
);
|
||||||
@@ -102,7 +116,7 @@ impl Client {
|
|||||||
let child = ui
|
let child = ui
|
||||||
.add(image(include_bytes!("assets/sungals.png")))
|
.add(image(include_bytes!("assets/sungals.png")))
|
||||||
.erase_type();
|
.erase_type();
|
||||||
ui[span_add].children.push((child, ratio(1)));
|
ui[span_add].children.push((child, sized()));
|
||||||
})
|
})
|
||||||
.region(
|
.region(
|
||||||
UiPos::corner(Corner::BotRight)
|
UiPos::corner(Corner::BotRight)
|
||||||
@@ -134,7 +148,7 @@ impl Client {
|
|||||||
main,
|
main,
|
||||||
add_button.label("add button"),
|
add_button.label("add button"),
|
||||||
del_button.label("del button"),
|
del_button.label("del button"),
|
||||||
info_sect.label("info sect"),
|
// info_sect.label("info sect"),
|
||||||
)
|
)
|
||||||
.stack()
|
.stack()
|
||||||
.label("main stack"),
|
.label("main stack"),
|
||||||
|
|||||||
Reference in New Issue
Block a user