initial text wrapping impl (resizing will break)
This commit is contained in:
@@ -44,11 +44,8 @@ impl Text {
|
|||||||
UiRegion::from_size_align(self.size, self.align)
|
UiRegion::from_size_align(self.size, self.align)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_buf(&mut self, font_system: &mut FontSystem) {
|
fn update_buf(&mut self, font_system: &mut FontSystem, width: Option<f32>) {
|
||||||
self.buf.set_metrics(
|
self.attrs.apply(font_system, &mut self.buf, width);
|
||||||
font_system,
|
|
||||||
Metrics::new(self.attrs.font_size, self.attrs.line_height),
|
|
||||||
);
|
|
||||||
if self.content.changed {
|
if self.content.changed {
|
||||||
self.content.changed = false;
|
self.content.changed = false;
|
||||||
self.buf.set_text(
|
self.buf.set_text(
|
||||||
@@ -63,8 +60,13 @@ impl Text {
|
|||||||
|
|
||||||
impl Widget for Text {
|
impl Widget for Text {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
|
let width = if self.attrs.wrap {
|
||||||
|
Some(painter.px_size().x)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
let font_system = &mut painter.text_data().font_system;
|
let font_system = &mut painter.text_data().font_system;
|
||||||
self.update_buf(font_system);
|
self.update_buf(font_system, width);
|
||||||
let (handle, offset) = painter.render_text(&mut self.buf, &self.attrs);
|
let (handle, offset) = painter.render_text(&mut self.buf, &self.attrs);
|
||||||
let dims = handle.size();
|
let dims = handle.size();
|
||||||
self.size = offset.size(&handle);
|
self.size = offset.size(&handle);
|
||||||
@@ -75,7 +77,7 @@ impl Widget for Text {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 {
|
fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 {
|
||||||
self.update_buf(&mut ctx.text.font_system);
|
self.update_buf(&mut ctx.text.font_system, None);
|
||||||
let (handle, offset) = ctx.draw_text(&mut self.buf, &self.attrs);
|
let (handle, offset) = ctx.draw_text(&mut self.buf, &self.attrs);
|
||||||
UiVec2::abs(offset.size(&handle))
|
UiVec2::abs(offset.size(&handle))
|
||||||
}
|
}
|
||||||
@@ -137,7 +139,7 @@ impl FnOnce<(&mut Ui,)> for TextBuilder {
|
|||||||
};
|
};
|
||||||
text.content.changed = false;
|
text.content.changed = false;
|
||||||
self.attrs
|
self.attrs
|
||||||
.apply(&mut args.0.text.font_system, &mut text.buf);
|
.apply(&mut args.0.text.font_system, &mut text.buf, None);
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,13 @@ impl TextEdit {
|
|||||||
|
|
||||||
impl Widget for TextEdit {
|
impl Widget for TextEdit {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
|
let width = if self.attrs.wrap {
|
||||||
|
Some(painter.px_size().x)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
let font_system = &mut painter.text_data().font_system;
|
let font_system = &mut painter.text_data().font_system;
|
||||||
self.attrs.apply(font_system, &mut self.buf);
|
self.attrs.apply(font_system, &mut self.buf, width);
|
||||||
self.buf.shape_until_scroll(font_system, false);
|
self.buf.shape_until_scroll(font_system, false);
|
||||||
let (handle, tex_offset) = painter.render_text(&mut self.buf, &self.attrs);
|
let (handle, tex_offset) = painter.render_text(&mut self.buf, &self.attrs);
|
||||||
let dims = handle.size();
|
let dims = handle.size();
|
||||||
@@ -36,10 +41,9 @@ impl Widget for TextEdit {
|
|||||||
painter.texture_within(&handle, tex_region);
|
painter.texture_within(&handle, tex_region);
|
||||||
|
|
||||||
if let Some(cursor) = &self.cursor
|
if let Some(cursor) = &self.cursor
|
||||||
&& let Some(pos) = cursor_pos(cursor, &self.buf)
|
&& let Some(offset) = cursor_pos(cursor, &self.buf)
|
||||||
{
|
{
|
||||||
let size = vec2(1, self.attrs.line_height);
|
let size = vec2(1, self.attrs.line_height);
|
||||||
let offset = vec2(pos, cursor.line as f32 * self.attrs.line_height);
|
|
||||||
painter.primitive_within(
|
painter.primitive_within(
|
||||||
RectPrimitive::color(Color::WHITE),
|
RectPrimitive::color(Color::WHITE),
|
||||||
UiRegion::from_size_align(size, Align::TopLeft)
|
UiRegion::from_size_align(size, Align::TopLeft)
|
||||||
@@ -53,17 +57,32 @@ impl Widget for TextEdit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 {
|
fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 {
|
||||||
|
let width = if self.attrs.wrap {
|
||||||
|
Some(ctx.px_size().x)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.attrs
|
||||||
|
.apply(&mut ctx.text.font_system, &mut self.buf, width);
|
||||||
|
self.buf
|
||||||
|
.shape_until_scroll(&mut ctx.text.font_system, false);
|
||||||
let (handle, offset) = ctx.draw_text(&mut self.buf, &self.attrs);
|
let (handle, offset) = ctx.draw_text(&mut self.buf, &self.attrs);
|
||||||
UiVec2::abs(offset.size(&handle))
|
UiVec2::abs(offset.size(&handle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// copied & modified from fn found in Editor in cosmic_text
|
/// copied & modified from fn found in Editor in cosmic_text
|
||||||
fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<f32> {
|
fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<(f32, f32)> {
|
||||||
let run = buf.layout_runs().find(|r| r.line_i == cursor.line)?;
|
let mut prev = None;
|
||||||
|
for run in buf
|
||||||
|
.layout_runs()
|
||||||
|
.skip_while(|r| r.line_i < cursor.line)
|
||||||
|
.take_while(|r| r.line_i == cursor.line)
|
||||||
|
{
|
||||||
|
prev = Some((run.line_w, run.line_top));
|
||||||
for glyph in run.glyphs.iter() {
|
for glyph in run.glyphs.iter() {
|
||||||
if cursor.index == glyph.start {
|
if cursor.index == glyph.start {
|
||||||
return Some(glyph.x);
|
return Some((glyph.x, run.line_top));
|
||||||
} else if cursor.index > glyph.start && cursor.index < glyph.end {
|
} else if cursor.index > glyph.start && cursor.index < glyph.end {
|
||||||
// Guess x offset based on characters
|
// Guess x offset based on characters
|
||||||
let mut before = 0;
|
let mut before = 0;
|
||||||
@@ -78,10 +97,11 @@ fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<f32> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let offset = glyph.w * (before as f32) / (total as f32);
|
let offset = glyph.w * (before as f32) / (total as f32);
|
||||||
return Some(glyph.x + offset);
|
return Some((glyph.x + offset, run.line_top));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(run.line_w)
|
}
|
||||||
|
prev
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextEditBuilder {
|
pub struct TextEditBuilder {
|
||||||
@@ -113,6 +133,10 @@ impl TextEditBuilder {
|
|||||||
self.align = align;
|
self.align = align;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub fn wrap(mut self, wrap: bool) -> Self {
|
||||||
|
self.attrs.wrap = wrap;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextEditCtx<'a> {
|
pub struct TextEditCtx<'a> {
|
||||||
@@ -305,7 +329,7 @@ impl FnOnce<(&mut Ui,)> for TextEditBuilder {
|
|||||||
Shaping::Advanced,
|
Shaping::Advanced,
|
||||||
);
|
);
|
||||||
self.attrs
|
self.attrs
|
||||||
.apply(&mut args.0.text.font_system, &mut text.buf);
|
.apply(&mut args.0.text.font_system, &mut text.buf, None);
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ pub struct Painter<'a, 'c> {
|
|||||||
primitives: Vec<PrimitiveHandle>,
|
primitives: Vec<PrimitiveHandle>,
|
||||||
children: Vec<Id>,
|
children: Vec<Id>,
|
||||||
sized_children: HashMap<Id, UiVec2>,
|
sized_children: HashMap<Id, UiVec2>,
|
||||||
|
/// whether this widget depends on region's final pixel size or not
|
||||||
|
/// TODO: decide if point (pt) should be used here instead of px
|
||||||
|
px_dependent: bool,
|
||||||
pub layer: usize,
|
pub layer: usize,
|
||||||
id: Id,
|
id: Id,
|
||||||
}
|
}
|
||||||
@@ -75,8 +78,10 @@ impl<'a> PainterCtx<'a> {
|
|||||||
text: self.text,
|
text: self.text,
|
||||||
textures: self.textures,
|
textures: self.textures,
|
||||||
widgets: self.widgets,
|
widgets: self.widgets,
|
||||||
|
region: UiRegion::full(),
|
||||||
|
screen_size: self.screen_size,
|
||||||
};
|
};
|
||||||
let desired = ctx.size_inner(id);
|
let desired = ctx.size_inner(id, active.region);
|
||||||
if size != desired {
|
if size != desired {
|
||||||
self.redraw(rid);
|
self.redraw(rid);
|
||||||
if self.drawing.contains(&id) {
|
if self.drawing.contains(&id) {
|
||||||
@@ -152,6 +157,7 @@ impl<'a> PainterCtx<'a> {
|
|||||||
ctx: self,
|
ctx: self,
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
sized_children: Default::default(),
|
sized_children: Default::default(),
|
||||||
|
px_dependent: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// draw widgets
|
// draw widgets
|
||||||
@@ -305,9 +311,16 @@ impl<'a, 'c> Painter<'a, 'c> {
|
|||||||
textures: self.ctx.textures,
|
textures: self.ctx.textures,
|
||||||
widgets: self.ctx.widgets,
|
widgets: self.ctx.widgets,
|
||||||
checked: &mut self.sized_children,
|
checked: &mut self.sized_children,
|
||||||
|
screen_size: self.ctx.screen_size,
|
||||||
|
region: self.region,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn px_size(&mut self) -> Vec2 {
|
||||||
|
self.px_dependent = true;
|
||||||
|
self.region.in_size(self.ctx.screen_size)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn text_data(&mut self) -> &mut TextData {
|
pub fn text_data(&mut self) -> &mut TextData {
|
||||||
self.ctx.text
|
self.ctx.text
|
||||||
}
|
}
|
||||||
@@ -326,11 +339,16 @@ pub struct SizeCtx<'a> {
|
|||||||
pub textures: &'a mut Textures,
|
pub textures: &'a mut Textures,
|
||||||
widgets: &'a Widgets,
|
widgets: &'a Widgets,
|
||||||
checked: &'a mut HashMap<Id, UiVec2>,
|
checked: &'a mut HashMap<Id, UiVec2>,
|
||||||
|
region: UiRegion,
|
||||||
|
screen_size: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SizeCtx<'_> {
|
impl SizeCtx<'_> {
|
||||||
fn size_inner(&mut self, id: Id) -> UiVec2 {
|
fn size_inner(&mut self, id: Id, region: UiRegion) -> UiVec2 {
|
||||||
|
let self_region = self.region;
|
||||||
|
self.region = region;
|
||||||
let size = self.widgets.get_dyn_dynamic(id).desired_size(self);
|
let size = self.widgets.get_dyn_dynamic(id).desired_size(self);
|
||||||
|
self.region = self_region;
|
||||||
self.checked.insert(id, size);
|
self.checked.insert(id, size);
|
||||||
size
|
size
|
||||||
}
|
}
|
||||||
@@ -339,7 +357,13 @@ impl SizeCtx<'_> {
|
|||||||
// if let Some(size) = self.checked.get(&id.id) {
|
// if let Some(size) = self.checked.get(&id.id) {
|
||||||
// return Some(*size);
|
// return Some(*size);
|
||||||
// }
|
// }
|
||||||
self.size_inner(id.id)
|
self.size_inner(id.id, self.region)
|
||||||
|
}
|
||||||
|
pub fn size_within<W>(&mut self, id: &WidgetId<W>, region: UiRegion) -> UiVec2 {
|
||||||
|
self.size_inner(id.id, region.within(&self.region))
|
||||||
|
}
|
||||||
|
pub fn px_size(&self) -> Vec2 {
|
||||||
|
self.region.in_size(self.screen_size)
|
||||||
}
|
}
|
||||||
pub fn draw_text(
|
pub fn draw_text(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
use std::marker::Destruct;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
layout::{Align, Axis, Vec2},
|
layout::{Align, Axis, UiNum, Vec2},
|
||||||
util::{LerpUtil, impl_op},
|
util::{LerpUtil, impl_op},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -25,16 +27,16 @@ impl UiVec2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn abs(abs: Vec2) -> Self {
|
pub const fn abs(abs: impl const Into<Vec2>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rel: Vec2::ZERO,
|
rel: Vec2::ZERO,
|
||||||
abs,
|
abs: abs.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn rel(rel: Vec2) -> Self {
|
pub const fn rel(rel: impl const Into<Vec2>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rel,
|
rel: rel.into(),
|
||||||
abs: Vec2::ZERO,
|
abs: Vec2::ZERO,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,6 +135,15 @@ impl const From<Vec2> for UiVec2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for UiVec2
|
||||||
|
where
|
||||||
|
(T, U): const Destruct,
|
||||||
|
{
|
||||||
|
fn from(abs: (T, U)) -> Self {
|
||||||
|
Self::abs(abs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub struct UiScalar {
|
pub struct UiScalar {
|
||||||
pub rel: f32,
|
pub rel: f32,
|
||||||
|
|||||||
@@ -26,11 +26,17 @@ pub struct TextAttrs {
|
|||||||
pub font_size: f32,
|
pub font_size: f32,
|
||||||
pub line_height: f32,
|
pub line_height: f32,
|
||||||
pub family: Family<'static>,
|
pub family: Family<'static>,
|
||||||
|
pub wrap: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextAttrs {
|
impl TextAttrs {
|
||||||
pub fn apply(&self, font_system: &mut FontSystem, buf: &mut Buffer) {
|
pub fn apply(&self, font_system: &mut FontSystem, buf: &mut Buffer, width: Option<f32>) {
|
||||||
buf.set_metrics(font_system, Metrics::new(self.font_size, self.line_height));
|
buf.set_metrics_and_size(
|
||||||
|
font_system,
|
||||||
|
Metrics::new(self.font_size, self.line_height),
|
||||||
|
width,
|
||||||
|
None,
|
||||||
|
);
|
||||||
let attrs = Attrs::new().family(self.family);
|
let attrs = Attrs::new().family(self.family);
|
||||||
let list = AttrsList::new(&attrs);
|
let list = AttrsList::new(&attrs);
|
||||||
for line in &mut buf.lines {
|
for line in &mut buf.lines {
|
||||||
@@ -49,6 +55,7 @@ impl Default for TextAttrs {
|
|||||||
font_size: size,
|
font_size: size,
|
||||||
line_height: size * 1.2,
|
line_height: size * 1.2,
|
||||||
family: Family::SansSerif,
|
family: Family::SansSerif,
|
||||||
|
wrap: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,6 +77,7 @@ impl TextData {
|
|||||||
cosmic_text::Color::rgba(c.r, c.g, c.b, c.a)
|
cosmic_text::Color::rgba(c.r, c.g, c.b, c.a)
|
||||||
};
|
};
|
||||||
let mut max_width = 0.0f32;
|
let mut max_width = 0.0f32;
|
||||||
|
let mut height = 0.0;
|
||||||
for run in buffer.layout_runs() {
|
for run in buffer.layout_runs() {
|
||||||
for glyph in run.glyphs.iter() {
|
for glyph in run.glyphs.iter() {
|
||||||
let physical_glyph = glyph.physical((0., 0.), 1.0);
|
let physical_glyph = glyph.physical((0., 0.), 1.0);
|
||||||
@@ -95,22 +103,19 @@ impl TextData {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
max_width = max_width.max(run.line_w);
|
max_width = max_width.max(run.line_w);
|
||||||
|
height += run.line_height;
|
||||||
}
|
}
|
||||||
let width = (max_x - min_x + 1) as u32;
|
let img_width = (max_x - min_x + 1) as u32;
|
||||||
let height = (max_y - min_y + 1) as u32;
|
let img_height = (max_y - min_y + 1) as u32;
|
||||||
let mut image = RgbaImage::new(width, height);
|
let mut image = RgbaImage::new(img_width, img_height);
|
||||||
for ((x, y), color) in pixels {
|
for ((x, y), color) in pixels {
|
||||||
let x = (x - min_x) as u32;
|
let x = (x - min_x) as u32;
|
||||||
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);
|
||||||
}
|
}
|
||||||
let lines = buffer.lines.len();
|
|
||||||
let offset = TextOffset {
|
let offset = TextOffset {
|
||||||
top_left: Vec2::new(min_x as f32, min_y as f32),
|
top_left: Vec2::new(min_x as f32, min_y as f32),
|
||||||
bot_right: Vec2::new(
|
bot_right: Vec2::new(max_width - max_x as f32, height - max_y as f32),
|
||||||
max_width - max_x as f32,
|
|
||||||
attrs.line_height * lines as f32 - max_y as f32,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
(textures.add(image), offset)
|
(textures.add(image), offset)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::{
|
|||||||
layout::UiNum,
|
layout::UiNum,
|
||||||
util::{DivOr, impl_op},
|
util::{DivOr, impl_op},
|
||||||
};
|
};
|
||||||
use std::ops::*;
|
use std::{marker::Destruct, ops::*};
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
@@ -79,7 +79,10 @@ impl Neg for Vec2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: UiNum, U: UiNum> From<(T, U)> for Vec2 {
|
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for Vec2
|
||||||
|
where
|
||||||
|
(T, U): const Destruct,
|
||||||
|
{
|
||||||
fn from((x, y): (T, U)) -> Self {
|
fn from((x, y): (T, U)) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: x.to_f32(),
|
x: x.to_f32(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#![feature(unboxed_closures)]
|
#![feature(unboxed_closures)]
|
||||||
#![feature(fn_traits)]
|
#![feature(fn_traits)]
|
||||||
#![feature(const_cmp)]
|
#![feature(const_cmp)]
|
||||||
|
#![feature(const_destruct)]
|
||||||
|
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
|||||||
@@ -139,13 +139,14 @@ impl Client {
|
|||||||
})
|
})
|
||||||
.id_on(Submit, move |id, client: &mut Client, _| {
|
.id_on(Submit, move |id, client: &mut Client, _| {
|
||||||
let content = client.ui.text(id).take();
|
let content = client.ui.text(id).take();
|
||||||
let text = text_edit(content).size(30).text_align(Align::Left).id_on(
|
let text = text_edit(content)
|
||||||
CursorSense::click(),
|
.size(30)
|
||||||
|id, client: &mut Client, ctx| {
|
.text_align(Align::Left)
|
||||||
|
.wrap(true)
|
||||||
|
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| {
|
||||||
client.ui.text(id).select(ctx.cursor, ctx.size);
|
client.ui.text(id).select(ctx.cursor, ctx.size);
|
||||||
client.focus = Some(id.clone());
|
client.focus = Some(id.clone());
|
||||||
},
|
});
|
||||||
);
|
|
||||||
let msg_box = (rect(Color::WHITE.darker(0.5)), text)
|
let msg_box = (rect(Color::WHITE.darker(0.5)), text)
|
||||||
.stack()
|
.stack()
|
||||||
.size(StackSize::Child(1))
|
.size(StackSize::Child(1))
|
||||||
|
|||||||
Reference in New Issue
Block a user