Restructure mod paint
This commit is contained in:
parent
fb2317c993
commit
c0041d032a
|
@ -8,7 +8,7 @@ use std::sync::{
|
|||
use crate::{
|
||||
animation_manager::AnimationManager,
|
||||
mutex::{Mutex, MutexGuard},
|
||||
paint::{stats::*, *},
|
||||
paint::{stats::*, text::Fonts, *},
|
||||
*,
|
||||
};
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ impl Texture {
|
|||
}
|
||||
}
|
||||
|
||||
impl paint::FontDefinitions {
|
||||
impl paint::text::FontDefinitions {
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
for (text_style, (_family, size)) in self.family_and_size.iter_mut() {
|
||||
// TODO: radio button for family
|
||||
|
|
|
@ -108,8 +108,9 @@ pub use {
|
|||
math::{clamp, lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rect, Vec2},
|
||||
memory::Memory,
|
||||
paint::{
|
||||
color, Color32, FontDefinitions, FontFamily, PaintJobs, Rgba, Shape, Stroke, TextStyle,
|
||||
Texture, TextureId,
|
||||
color,
|
||||
text::{FontDefinitions, FontFamily, TextStyle},
|
||||
Color32, PaintJobs, Rgba, Shape, Stroke, Texture, TextureId,
|
||||
},
|
||||
painter::Painter,
|
||||
style::Style,
|
||||
|
|
|
@ -68,7 +68,7 @@ pub(crate) struct Options {
|
|||
/// Controls the tessellator.
|
||||
pub(crate) tessellation_options: crate::paint::TessellationOptions,
|
||||
/// Font sizes etc.
|
||||
pub(crate) font_definitions: crate::paint::FontDefinitions,
|
||||
pub(crate) font_definitions: crate::paint::text::FontDefinitions,
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
@ -1,28 +1,51 @@
|
|||
//! 2D graphics/rendering. Fonts, textures, color, geometry, tessellation etc.
|
||||
|
||||
pub mod color;
|
||||
pub mod font;
|
||||
pub mod fonts;
|
||||
mod galley;
|
||||
mod shadow;
|
||||
pub mod shape;
|
||||
pub mod stats;
|
||||
mod stroke;
|
||||
pub mod tessellator;
|
||||
pub mod text;
|
||||
mod texture_atlas;
|
||||
mod triangles;
|
||||
|
||||
pub use {
|
||||
color::{Color32, Rgba},
|
||||
fonts::{FontDefinitions, FontFamily, Fonts, TextStyle},
|
||||
galley::*,
|
||||
shadow::Shadow,
|
||||
shape::{Shape, Stroke},
|
||||
shape::Shape,
|
||||
stats::PaintStats,
|
||||
tessellator::{
|
||||
PaintJob, PaintJobs, TessellationOptions, TextureId, Triangles, Vertex, WHITE_UV,
|
||||
},
|
||||
stroke::Stroke,
|
||||
tessellator::{PaintJob, PaintJobs, TessellationOptions},
|
||||
text::{Galley, TextStyle},
|
||||
texture_atlas::{Texture, TextureAtlas},
|
||||
triangles::{Triangles, Vertex},
|
||||
};
|
||||
|
||||
/// The UV coordinate of a white region of the texture mesh.
|
||||
/// The default Egui texture has the top-left corner pixel fully white.
|
||||
/// You need need use a clamping texture sampler for this to work
|
||||
/// (so it doesn't do bilinear blending with bottom right corner).
|
||||
pub const WHITE_UV: crate::Pos2 = crate::pos2(0.0, 0.0);
|
||||
|
||||
/// What texture to use in a [`Triangles`] mesh.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum TextureId {
|
||||
/// The Egui font texture.
|
||||
/// If you don't want to use a texture, pick this and the [`WHITE_UV`] for uv-coord.
|
||||
Egui,
|
||||
|
||||
/// Your own texture, defined in any which way you want.
|
||||
/// Egui won't care. The backend renderer will presumably use this to look up what texture to use.
|
||||
User(u64),
|
||||
}
|
||||
|
||||
impl Default for TextureId {
|
||||
fn default() -> Self {
|
||||
Self::Egui
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PaintRect {
|
||||
pub rect: crate::Rect,
|
||||
/// How rounded the corners are. Use `0.0` for no rounding.
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use {
|
||||
super::{fonts::TextStyle, Color32, Fonts, Galley, Triangles},
|
||||
super::{
|
||||
text::{Fonts, Galley, TextStyle},
|
||||
Color32, Triangles,
|
||||
},
|
||||
crate::*,
|
||||
};
|
||||
|
||||
|
@ -189,33 +192,3 @@ impl Shape {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the width and color of a line.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Stroke {
|
||||
pub width: f32,
|
||||
pub color: Color32,
|
||||
}
|
||||
|
||||
impl Stroke {
|
||||
pub fn none() -> Self {
|
||||
Self::new(0.0, Color32::TRANSPARENT)
|
||||
}
|
||||
|
||||
pub fn new(width: impl Into<f32>, color: impl Into<Color32>) -> Self {
|
||||
Self {
|
||||
width: width.into(),
|
||||
color: color.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Color> From<(f32, Color)> for Stroke
|
||||
where
|
||||
Color: Into<Color32>,
|
||||
{
|
||||
fn from((width, color): (f32, Color)) -> Stroke {
|
||||
Stroke::new(width, color)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
use super::*;
|
||||
|
||||
/// Describes the width and color of a line.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Stroke {
|
||||
pub width: f32,
|
||||
pub color: Color32,
|
||||
}
|
||||
|
||||
impl Stroke {
|
||||
pub fn none() -> Self {
|
||||
Self::new(0.0, Color32::TRANSPARENT)
|
||||
}
|
||||
|
||||
pub fn new(width: impl Into<f32>, color: impl Into<Color32>) -> Self {
|
||||
Self {
|
||||
width: width.into(),
|
||||
color: color.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Color> From<(f32, Color)> for Stroke
|
||||
where
|
||||
Color: Into<Color32>,
|
||||
{
|
||||
fn from((width, color): (f32, Color)) -> Stroke {
|
||||
Stroke::new(width, color)
|
||||
}
|
||||
}
|
|
@ -6,70 +6,11 @@
|
|||
#![allow(clippy::identity_op)]
|
||||
|
||||
use {
|
||||
super::{
|
||||
color::{Color32, Rgba},
|
||||
*,
|
||||
},
|
||||
super::{text::Fonts, *},
|
||||
crate::math::*,
|
||||
std::f32::consts::TAU,
|
||||
};
|
||||
|
||||
/// What texture to use in a [`Triangles`] mesh.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum TextureId {
|
||||
/// The Egui font texture.
|
||||
/// If you don't want to use a texture, pick this and the [`WHITE_UV`] for uv-coord.
|
||||
Egui,
|
||||
|
||||
/// Your own texture, defined in any which way you want.
|
||||
/// Egui won't care. The backend renderer will presumably use this to look up what texture to use.
|
||||
User(u64),
|
||||
}
|
||||
|
||||
impl Default for TextureId {
|
||||
fn default() -> Self {
|
||||
Self::Egui
|
||||
}
|
||||
}
|
||||
|
||||
/// The UV coordinate of a white region of the texture mesh.
|
||||
/// The default Egui texture has the top-left corner pixel fully white.
|
||||
/// You need need use a clamping texture sampler for this to work
|
||||
/// (so it doesn't do bilinear blending with bottom right corner).
|
||||
pub const WHITE_UV: Pos2 = pos2(0.0, 0.0);
|
||||
|
||||
/// The vertex type.
|
||||
///
|
||||
/// Should be friendly to send to GPU as is.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Vertex {
|
||||
/// Logical pixel coordinates (points).
|
||||
/// (0,0) is the top left corner of the screen.
|
||||
pub pos: Pos2, // 64 bit
|
||||
|
||||
/// Normalized texture coordinates.
|
||||
/// (0, 0) is the top left corner of the texture.
|
||||
/// (1, 1) is the bottom right corner of the texture.
|
||||
pub uv: Pos2, // 64 bit
|
||||
|
||||
/// sRGBA with premultiplied alpha
|
||||
pub color: Color32, // 32 bit
|
||||
}
|
||||
|
||||
/// Textured triangles.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Triangles {
|
||||
/// Draw as triangles (i.e. the length is always multiple of three).
|
||||
pub indices: Vec<u32>,
|
||||
|
||||
/// The vertex data indexed by `indices`.
|
||||
pub vertices: Vec<Vertex>,
|
||||
|
||||
/// The texture to use when drawing these triangles
|
||||
pub texture_id: TextureId,
|
||||
}
|
||||
|
||||
/// A clip triangle and some textured triangles.
|
||||
pub type PaintJob = (Rect, Triangles);
|
||||
|
||||
|
@ -78,182 +19,6 @@ pub type PaintJobs = Vec<PaintJob>;
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// ## Helpers for adding
|
||||
impl Triangles {
|
||||
pub fn with_texture(texture_id: TextureId) -> Self {
|
||||
Self {
|
||||
texture_id,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes_used(&self) -> usize {
|
||||
std::mem::size_of::<Self>()
|
||||
+ self.vertices.len() * std::mem::size_of::<Vertex>()
|
||||
+ self.indices.len() * std::mem::size_of::<u32>()
|
||||
}
|
||||
|
||||
/// Are all indices within the bounds of the contained vertices?
|
||||
pub fn is_valid(&self) -> bool {
|
||||
let n = self.vertices.len() as u32;
|
||||
self.indices.iter().all(|&i| i < n)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.indices.is_empty() && self.vertices.is_empty()
|
||||
}
|
||||
|
||||
/// Append all the indices and vertices of `other` to `self`.
|
||||
pub fn append(&mut self, other: Triangles) {
|
||||
debug_assert!(other.is_valid());
|
||||
|
||||
if self.is_empty() {
|
||||
*self = other;
|
||||
} else {
|
||||
assert_eq!(
|
||||
self.texture_id, other.texture_id,
|
||||
"Can't merge Triangles using different textures"
|
||||
);
|
||||
|
||||
let index_offset = self.vertices.len() as u32;
|
||||
for index in &other.indices {
|
||||
self.indices.push(index_offset + index);
|
||||
}
|
||||
self.vertices.extend(other.vertices.iter());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn colored_vertex(&mut self, pos: Pos2, color: Color32) {
|
||||
debug_assert!(self.texture_id == TextureId::Egui);
|
||||
self.vertices.push(Vertex {
|
||||
pos,
|
||||
uv: WHITE_UV,
|
||||
color,
|
||||
});
|
||||
}
|
||||
|
||||
/// Add a triangle.
|
||||
pub fn add_triangle(&mut self, a: u32, b: u32, c: u32) {
|
||||
self.indices.push(a);
|
||||
self.indices.push(b);
|
||||
self.indices.push(c);
|
||||
}
|
||||
|
||||
/// Make room for this many additional triangles (will reserve 3x as many indices).
|
||||
/// See also `reserve_vertices`.
|
||||
pub fn reserve_triangles(&mut self, additional_triangles: usize) {
|
||||
self.indices.reserve(3 * additional_triangles);
|
||||
}
|
||||
|
||||
/// Make room for this many additional vertices.
|
||||
/// See also `reserve_triangles`.
|
||||
pub fn reserve_vertices(&mut self, additional: usize) {
|
||||
self.vertices.reserve(additional);
|
||||
}
|
||||
|
||||
/// Rectangle with a texture and color.
|
||||
pub fn add_rect_with_uv(&mut self, pos: Rect, uv: Rect, color: Color32) {
|
||||
let idx = self.vertices.len() as u32;
|
||||
self.add_triangle(idx + 0, idx + 1, idx + 2);
|
||||
self.add_triangle(idx + 2, idx + 1, idx + 3);
|
||||
|
||||
let right_top = Vertex {
|
||||
pos: pos.right_top(),
|
||||
uv: uv.right_top(),
|
||||
color,
|
||||
};
|
||||
let left_top = Vertex {
|
||||
pos: pos.left_top(),
|
||||
uv: uv.left_top(),
|
||||
color,
|
||||
};
|
||||
let left_bottom = Vertex {
|
||||
pos: pos.left_bottom(),
|
||||
uv: uv.left_bottom(),
|
||||
color,
|
||||
};
|
||||
let right_bottom = Vertex {
|
||||
pos: pos.right_bottom(),
|
||||
uv: uv.right_bottom(),
|
||||
color,
|
||||
};
|
||||
self.vertices.push(left_top);
|
||||
self.vertices.push(right_top);
|
||||
self.vertices.push(left_bottom);
|
||||
self.vertices.push(right_bottom);
|
||||
}
|
||||
|
||||
/// Uniformly colored rectangle.
|
||||
pub fn add_colored_rect(&mut self, rect: Rect, color: Color32) {
|
||||
debug_assert!(self.texture_id == TextureId::Egui);
|
||||
self.add_rect_with_uv(rect, [WHITE_UV, WHITE_UV].into(), color)
|
||||
}
|
||||
|
||||
/// This is for platforms that only support 16-bit index buffers.
|
||||
///
|
||||
/// Splits this mesh into many smaller meshes (if needed).
|
||||
/// All the returned meshes will have indices that fit into a `u16`.
|
||||
pub fn split_to_u16(self) -> Vec<Triangles> {
|
||||
const MAX_SIZE: u32 = 1 << 16;
|
||||
|
||||
if self.vertices.len() < MAX_SIZE as usize {
|
||||
return vec![self]; // Common-case optimization
|
||||
}
|
||||
|
||||
let mut output = vec![];
|
||||
let mut index_cursor = 0;
|
||||
|
||||
while index_cursor < self.indices.len() {
|
||||
let span_start = index_cursor;
|
||||
let mut min_vindex = self.indices[index_cursor];
|
||||
let mut max_vindex = self.indices[index_cursor];
|
||||
|
||||
while index_cursor < self.indices.len() {
|
||||
let (mut new_min, mut new_max) = (min_vindex, max_vindex);
|
||||
for i in 0..3 {
|
||||
let idx = self.indices[index_cursor + i];
|
||||
new_min = new_min.min(idx);
|
||||
new_max = new_max.max(idx);
|
||||
}
|
||||
|
||||
if new_max - new_min < MAX_SIZE {
|
||||
// Triangle fits
|
||||
min_vindex = new_min;
|
||||
max_vindex = new_max;
|
||||
index_cursor += 3;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
index_cursor > span_start,
|
||||
"One triangle spanned more than {} vertices",
|
||||
MAX_SIZE
|
||||
);
|
||||
|
||||
output.push(Triangles {
|
||||
indices: self.indices[span_start..index_cursor]
|
||||
.iter()
|
||||
.map(|vi| vi - min_vindex)
|
||||
.collect(),
|
||||
vertices: self.vertices[(min_vindex as usize)..=(max_vindex as usize)].to_vec(),
|
||||
texture_id: self.texture_id,
|
||||
});
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
/// Translate location by this much, in-place
|
||||
pub fn translate(&mut self, delta: Vec2) {
|
||||
for v in &mut self.vertices {
|
||||
v.pos += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PathPoint {
|
||||
pos: Pos2,
|
||||
|
@ -669,10 +434,11 @@ fn mul_color(color: Color32, factor: f32) -> Color32 {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Converts [`Shape`]s into [`Triangles`].
|
||||
pub struct Tessellator {
|
||||
pub options: TessellationOptions,
|
||||
options: TessellationOptions,
|
||||
/// Only used for culling
|
||||
pub clip_rect: Rect,
|
||||
clip_rect: Rect,
|
||||
scratchpad_points: Vec<Pos2>,
|
||||
scratchpad_path: Path,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
//! Different types of text cursors, i.e. ways to point into a [`super::Galley`].
|
||||
|
||||
/// Character cursor
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct CCursor {
|
||||
/// Character offset (NOT byte offset!).
|
||||
pub index: usize,
|
||||
|
||||
/// If this cursors sits right at the border of a wrapped row break (NOT paragraph break)
|
||||
/// do we prefer the next row?
|
||||
/// This is *almost* always what you want, *except* for when
|
||||
/// explicitly clicking the end of a row or pressing the end key.
|
||||
pub prefer_next_row: bool,
|
||||
}
|
||||
|
||||
impl CCursor {
|
||||
pub fn new(index: usize) -> Self {
|
||||
Self {
|
||||
index,
|
||||
prefer_next_row: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Two `CCursor`s are considered equal if they refer to the same character boundary,
|
||||
/// even if one prefers the start of the next row.
|
||||
impl PartialEq for CCursor {
|
||||
fn eq(&self, other: &CCursor) -> bool {
|
||||
self.index == other.index
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<usize> for CCursor {
|
||||
type Output = CCursor;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
CCursor {
|
||||
index: self.index.saturating_add(rhs),
|
||||
prefer_next_row: self.prefer_next_row,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<usize> for CCursor {
|
||||
type Output = CCursor;
|
||||
fn sub(self, rhs: usize) -> Self::Output {
|
||||
CCursor {
|
||||
index: self.index.saturating_sub(rhs),
|
||||
prefer_next_row: self.prefer_next_row,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Row Cursor
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct RCursor {
|
||||
/// 0 is first row, and so on.
|
||||
/// Note that a single paragraph can span multiple rows.
|
||||
/// (a paragraph is text separated by `\n`).
|
||||
pub row: usize,
|
||||
|
||||
/// Character based (NOT bytes).
|
||||
/// It is fine if this points to something beyond the end of the current row.
|
||||
/// When moving up/down it may again be within the next row.
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
/// Paragraph Cursor
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct PCursor {
|
||||
/// 0 is first paragraph, and so on.
|
||||
/// Note that a single paragraph can span multiple rows.
|
||||
/// (a paragraph is text separated by `\n`).
|
||||
pub paragraph: usize,
|
||||
|
||||
/// Character based (NOT bytes).
|
||||
/// It is fine if this points to something beyond the end of the current paragraph.
|
||||
/// When moving up/down it may again be within the next paragraph.
|
||||
pub offset: usize,
|
||||
|
||||
/// If this cursors sits right at the border of a wrapped row break (NOT paragraph break)
|
||||
/// do we prefer the next row?
|
||||
/// This is *almost* always what you want, *except* for when
|
||||
/// explicitly clicking the end of a row or pressing the end key.
|
||||
pub prefer_next_row: bool,
|
||||
}
|
||||
|
||||
/// Two `PCursor`s are considered equal if they refer to the same character boundary,
|
||||
/// even if one prefers the start of the next row.
|
||||
impl PartialEq for PCursor {
|
||||
fn eq(&self, other: &PCursor) -> bool {
|
||||
self.paragraph == other.paragraph && self.offset == other.offset
|
||||
}
|
||||
}
|
||||
|
||||
/// All different types of cursors together.
|
||||
/// They all point to the same place, but in their own different ways.
|
||||
/// pcursor/rcursor can also point to after the end of the paragraph/row.
|
||||
/// Does not implement `PartialEq` because you must think which cursor should be equivalent.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Cursor {
|
||||
pub ccursor: CCursor,
|
||||
pub rcursor: RCursor,
|
||||
pub pcursor: PCursor,
|
||||
}
|
|
@ -8,11 +8,12 @@ use {
|
|||
use crate::{
|
||||
math::{vec2, Vec2},
|
||||
mutex::{Mutex, RwLock},
|
||||
paint::{Galley, Row},
|
||||
paint::{
|
||||
text::galley::{Galley, Row},
|
||||
TextureAtlas,
|
||||
},
|
||||
};
|
||||
|
||||
use super::texture_atlas::TextureAtlas;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
|
@ -4,12 +4,9 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
|
||||
use super::font::{Font, FontImpl};
|
||||
use crate::mutex::Mutex;
|
||||
|
||||
use super::{
|
||||
font::{Font, FontImpl},
|
||||
texture_atlas::{Texture, TextureAtlas},
|
||||
};
|
||||
use crate::paint::{Texture, TextureAtlas};
|
||||
|
||||
// TODO: rename
|
||||
/// One of a few categories of styles of text, e.g. body, button or heading.
|
||||
|
@ -118,22 +115,22 @@ impl Default for FontDefinitions {
|
|||
// Use size 13 for this. NOTHING ELSE:
|
||||
font_data.insert(
|
||||
"ProggyClean".to_owned(),
|
||||
std::borrow::Cow::Borrowed(include_bytes!("../../fonts/ProggyClean.ttf")),
|
||||
std::borrow::Cow::Borrowed(include_bytes!("../../../fonts/ProggyClean.ttf")),
|
||||
);
|
||||
font_data.insert(
|
||||
"Ubuntu-Light".to_owned(),
|
||||
std::borrow::Cow::Borrowed(include_bytes!("../../fonts/Ubuntu-Light.ttf")),
|
||||
std::borrow::Cow::Borrowed(include_bytes!("../../../fonts/Ubuntu-Light.ttf")),
|
||||
);
|
||||
|
||||
// Some good looking emojis. Use as first priority:
|
||||
font_data.insert(
|
||||
"NotoEmoji-Regular".to_owned(),
|
||||
std::borrow::Cow::Borrowed(include_bytes!("../../fonts/NotoEmoji-Regular.ttf")),
|
||||
std::borrow::Cow::Borrowed(include_bytes!("../../../fonts/NotoEmoji-Regular.ttf")),
|
||||
);
|
||||
// Bigger emojis, and more. <http://jslegers.github.io/emoji-icon-font/>:
|
||||
font_data.insert(
|
||||
"emoji-icon-font".to_owned(),
|
||||
std::borrow::Cow::Borrowed(include_bytes!("../../fonts/emoji-icon-font.ttf")),
|
||||
std::borrow::Cow::Borrowed(include_bytes!("../../../fonts/emoji-icon-font.ttf")),
|
||||
);
|
||||
|
||||
fonts_for_family.insert(
|
|
@ -1,3 +1,6 @@
|
|||
//! A [`Galley`] is a piece of text after layout, i.e. where each character has been assigned a position.
|
||||
//!
|
||||
//! ## How it works
|
||||
//! This is going to get complicated.
|
||||
//!
|
||||
//! To avoid confusion, we never use the word "line".
|
||||
|
@ -14,117 +17,11 @@
|
|||
//!
|
||||
//! The offset `6` is both the end of the first row
|
||||
//! and the start of the second row.
|
||||
//! The `prefer_next_row` selects which.
|
||||
//! [`CCursor::prefer_next_row`] etc selects which.
|
||||
|
||||
use super::cursor::*;
|
||||
use crate::math::{pos2, NumExt, Rect, Vec2};
|
||||
|
||||
/// Character cursor
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct CCursor {
|
||||
/// Character offset (NOT byte offset!).
|
||||
pub index: usize,
|
||||
|
||||
/// If this cursors sits right at the border of a wrapped row break (NOT paragraph break)
|
||||
/// do we prefer the next row?
|
||||
/// This is *almost* always what you want, *except* for when
|
||||
/// explicitly clicking the end of a row or pressing the end key.
|
||||
pub prefer_next_row: bool,
|
||||
}
|
||||
|
||||
impl CCursor {
|
||||
pub fn new(index: usize) -> Self {
|
||||
Self {
|
||||
index,
|
||||
prefer_next_row: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Two `CCursor`s are considered equal if they refer to the same character boundary,
|
||||
/// even if one prefers the start of the next row.
|
||||
impl PartialEq for CCursor {
|
||||
fn eq(&self, other: &CCursor) -> bool {
|
||||
self.index == other.index
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<usize> for CCursor {
|
||||
type Output = CCursor;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
CCursor {
|
||||
index: self.index.saturating_add(rhs),
|
||||
prefer_next_row: self.prefer_next_row,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<usize> for CCursor {
|
||||
type Output = CCursor;
|
||||
fn sub(self, rhs: usize) -> Self::Output {
|
||||
CCursor {
|
||||
index: self.index.saturating_sub(rhs),
|
||||
prefer_next_row: self.prefer_next_row,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Row Cursor
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct RCursor {
|
||||
/// 0 is first row, and so on.
|
||||
/// Note that a single paragraph can span multiple rows.
|
||||
/// (a paragraph is text separated by `\n`).
|
||||
pub row: usize,
|
||||
|
||||
/// Character based (NOT bytes).
|
||||
/// It is fine if this points to something beyond the end of the current row.
|
||||
/// When moving up/down it may again be within the next row.
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
/// Paragraph Cursor
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct PCursor {
|
||||
/// 0 is first paragraph, and so on.
|
||||
/// Note that a single paragraph can span multiple rows.
|
||||
/// (a paragraph is text separated by `\n`).
|
||||
pub paragraph: usize,
|
||||
|
||||
/// Character based (NOT bytes).
|
||||
/// It is fine if this points to something beyond the end of the current paragraph.
|
||||
/// When moving up/down it may again be within the next paragraph.
|
||||
pub offset: usize,
|
||||
|
||||
/// If this cursors sits right at the border of a wrapped row break (NOT paragraph break)
|
||||
/// do we prefer the next row?
|
||||
/// This is *almost* always what you want, *except* for when
|
||||
/// explicitly clicking the end of a row or pressing the end key.
|
||||
pub prefer_next_row: bool,
|
||||
}
|
||||
|
||||
/// Two `PCursor`s are considered equal if they refer to the same character boundary,
|
||||
/// even if one prefers the start of the next row.
|
||||
impl PartialEq for PCursor {
|
||||
fn eq(&self, other: &PCursor) -> bool {
|
||||
self.paragraph == other.paragraph && self.offset == other.offset
|
||||
}
|
||||
}
|
||||
|
||||
/// All different types of cursors together.
|
||||
/// They all point to the same place, but in their own different ways.
|
||||
/// pcursor/rcursor can also point to after the end of the paragraph/row.
|
||||
/// Does not implement `PartialEq` because you must think which cursor should be equivalent.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Cursor {
|
||||
pub ccursor: CCursor,
|
||||
pub rcursor: RCursor,
|
||||
pub pcursor: PCursor,
|
||||
}
|
||||
|
||||
/// A collection of text locked into place.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Galley {
|
||||
|
@ -657,7 +554,7 @@ fn test_text_layout() {
|
|||
use crate::paint::*;
|
||||
|
||||
let pixels_per_point = 1.0;
|
||||
let fonts = Fonts::from_definitions(pixels_per_point, FontDefinitions::default());
|
||||
let fonts = text::Fonts::from_definitions(pixels_per_point, text::FontDefinitions::default());
|
||||
let font = &fonts[TextStyle::Monospace];
|
||||
|
||||
let galley = font.layout_multiline("".to_owned(), 1024.0);
|
|
@ -0,0 +1,9 @@
|
|||
pub mod cursor;
|
||||
mod font;
|
||||
mod fonts;
|
||||
mod galley;
|
||||
|
||||
pub use {
|
||||
fonts::{FontDefinitions, FontFamily, Fonts, TextStyle},
|
||||
galley::{Galley, Row},
|
||||
};
|
|
@ -0,0 +1,209 @@
|
|||
use super::*;
|
||||
use crate::*;
|
||||
|
||||
/// The vertex type.
|
||||
///
|
||||
/// Should be friendly to send to GPU as is.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Vertex {
|
||||
/// Logical pixel coordinates (points).
|
||||
/// (0,0) is the top left corner of the screen.
|
||||
pub pos: Pos2, // 64 bit
|
||||
|
||||
/// Normalized texture coordinates.
|
||||
/// (0, 0) is the top left corner of the texture.
|
||||
/// (1, 1) is the bottom right corner of the texture.
|
||||
pub uv: Pos2, // 64 bit
|
||||
|
||||
/// sRGBA with premultiplied alpha
|
||||
pub color: Color32, // 32 bit
|
||||
}
|
||||
|
||||
/// Textured triangles.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Triangles {
|
||||
/// Draw as triangles (i.e. the length is always multiple of three).
|
||||
pub indices: Vec<u32>,
|
||||
|
||||
/// The vertex data indexed by `indices`.
|
||||
pub vertices: Vec<Vertex>,
|
||||
|
||||
/// The texture to use when drawing these triangles
|
||||
pub texture_id: TextureId,
|
||||
}
|
||||
|
||||
impl Triangles {
|
||||
pub fn with_texture(texture_id: TextureId) -> Self {
|
||||
Self {
|
||||
texture_id,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes_used(&self) -> usize {
|
||||
std::mem::size_of::<Self>()
|
||||
+ self.vertices.len() * std::mem::size_of::<Vertex>()
|
||||
+ self.indices.len() * std::mem::size_of::<u32>()
|
||||
}
|
||||
|
||||
/// Are all indices within the bounds of the contained vertices?
|
||||
pub fn is_valid(&self) -> bool {
|
||||
let n = self.vertices.len() as u32;
|
||||
self.indices.iter().all(|&i| i < n)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.indices.is_empty() && self.vertices.is_empty()
|
||||
}
|
||||
|
||||
/// Append all the indices and vertices of `other` to `self`.
|
||||
pub fn append(&mut self, other: Triangles) {
|
||||
debug_assert!(other.is_valid());
|
||||
|
||||
if self.is_empty() {
|
||||
*self = other;
|
||||
} else {
|
||||
assert_eq!(
|
||||
self.texture_id, other.texture_id,
|
||||
"Can't merge Triangles using different textures"
|
||||
);
|
||||
|
||||
let index_offset = self.vertices.len() as u32;
|
||||
for index in &other.indices {
|
||||
self.indices.push(index_offset + index);
|
||||
}
|
||||
self.vertices.extend(other.vertices.iter());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn colored_vertex(&mut self, pos: Pos2, color: Color32) {
|
||||
debug_assert!(self.texture_id == TextureId::Egui);
|
||||
self.vertices.push(Vertex {
|
||||
pos,
|
||||
uv: WHITE_UV,
|
||||
color,
|
||||
});
|
||||
}
|
||||
|
||||
/// Add a triangle.
|
||||
pub fn add_triangle(&mut self, a: u32, b: u32, c: u32) {
|
||||
self.indices.push(a);
|
||||
self.indices.push(b);
|
||||
self.indices.push(c);
|
||||
}
|
||||
|
||||
/// Make room for this many additional triangles (will reserve 3x as many indices).
|
||||
/// See also `reserve_vertices`.
|
||||
pub fn reserve_triangles(&mut self, additional_triangles: usize) {
|
||||
self.indices.reserve(3 * additional_triangles);
|
||||
}
|
||||
|
||||
/// Make room for this many additional vertices.
|
||||
/// See also `reserve_triangles`.
|
||||
pub fn reserve_vertices(&mut self, additional: usize) {
|
||||
self.vertices.reserve(additional);
|
||||
}
|
||||
|
||||
/// Rectangle with a texture and color.
|
||||
pub fn add_rect_with_uv(&mut self, pos: Rect, uv: Rect, color: Color32) {
|
||||
#![allow(clippy::identity_op)]
|
||||
|
||||
let idx = self.vertices.len() as u32;
|
||||
self.add_triangle(idx + 0, idx + 1, idx + 2);
|
||||
self.add_triangle(idx + 2, idx + 1, idx + 3);
|
||||
|
||||
let right_top = Vertex {
|
||||
pos: pos.right_top(),
|
||||
uv: uv.right_top(),
|
||||
color,
|
||||
};
|
||||
let left_top = Vertex {
|
||||
pos: pos.left_top(),
|
||||
uv: uv.left_top(),
|
||||
color,
|
||||
};
|
||||
let left_bottom = Vertex {
|
||||
pos: pos.left_bottom(),
|
||||
uv: uv.left_bottom(),
|
||||
color,
|
||||
};
|
||||
let right_bottom = Vertex {
|
||||
pos: pos.right_bottom(),
|
||||
uv: uv.right_bottom(),
|
||||
color,
|
||||
};
|
||||
self.vertices.push(left_top);
|
||||
self.vertices.push(right_top);
|
||||
self.vertices.push(left_bottom);
|
||||
self.vertices.push(right_bottom);
|
||||
}
|
||||
|
||||
/// Uniformly colored rectangle.
|
||||
pub fn add_colored_rect(&mut self, rect: Rect, color: Color32) {
|
||||
debug_assert!(self.texture_id == TextureId::Egui);
|
||||
self.add_rect_with_uv(rect, [WHITE_UV, WHITE_UV].into(), color)
|
||||
}
|
||||
|
||||
/// This is for platforms that only support 16-bit index buffers.
|
||||
///
|
||||
/// Splits this mesh into many smaller meshes (if needed).
|
||||
/// All the returned meshes will have indices that fit into a `u16`.
|
||||
pub fn split_to_u16(self) -> Vec<Triangles> {
|
||||
const MAX_SIZE: u32 = 1 << 16;
|
||||
|
||||
if self.vertices.len() < MAX_SIZE as usize {
|
||||
return vec![self]; // Common-case optimization
|
||||
}
|
||||
|
||||
let mut output = vec![];
|
||||
let mut index_cursor = 0;
|
||||
|
||||
while index_cursor < self.indices.len() {
|
||||
let span_start = index_cursor;
|
||||
let mut min_vindex = self.indices[index_cursor];
|
||||
let mut max_vindex = self.indices[index_cursor];
|
||||
|
||||
while index_cursor < self.indices.len() {
|
||||
let (mut new_min, mut new_max) = (min_vindex, max_vindex);
|
||||
for i in 0..3 {
|
||||
let idx = self.indices[index_cursor + i];
|
||||
new_min = new_min.min(idx);
|
||||
new_max = new_max.max(idx);
|
||||
}
|
||||
|
||||
if new_max - new_min < MAX_SIZE {
|
||||
// Triangle fits
|
||||
min_vindex = new_min;
|
||||
max_vindex = new_max;
|
||||
index_cursor += 3;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
index_cursor > span_start,
|
||||
"One triangle spanned more than {} vertices",
|
||||
MAX_SIZE
|
||||
);
|
||||
|
||||
output.push(Triangles {
|
||||
indices: self.indices[span_start..index_cursor]
|
||||
.iter()
|
||||
.map(|vi| vi - min_vindex)
|
||||
.collect(),
|
||||
vertices: self.vertices[(min_vindex as usize)..=(max_vindex as usize)].to_vec(),
|
||||
texture_id: self.texture_id,
|
||||
});
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
/// Translate location by this much, in-place
|
||||
pub fn translate(&mut self, delta: Vec2) {
|
||||
for v in &mut self.vertices {
|
||||
v.pos += delta;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
use crate::{
|
||||
layers::ShapeIdx,
|
||||
math::{Align2, Pos2, Rect, Vec2},
|
||||
paint::{Fonts, Galley, Shape, Stroke, TextStyle},
|
||||
paint::{
|
||||
text::{Fonts, Galley, TextStyle},
|
||||
Shape, Stroke,
|
||||
},
|
||||
Color32, CtxRef, LayerId,
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,15 @@
|
|||
|
||||
use std::{hash::Hash, sync::Arc};
|
||||
|
||||
use crate::{color::*, containers::*, layout::*, mutex::MutexGuard, paint::*, widgets::*, *};
|
||||
use crate::{
|
||||
color::*,
|
||||
containers::*,
|
||||
layout::*,
|
||||
mutex::MutexGuard,
|
||||
paint::{text::Fonts, *},
|
||||
widgets::*,
|
||||
*,
|
||||
};
|
||||
|
||||
/// This is what you use to place widgets.
|
||||
///
|
||||
|
|
|
@ -73,7 +73,7 @@ impl Label {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn font_height(&self, fonts: &paint::Fonts, style: &Style) -> f32 {
|
||||
pub fn font_height(&self, fonts: &paint::text::Fonts, style: &Style) -> f32 {
|
||||
let text_style = self.text_style_or_default(style);
|
||||
fonts[text_style].row_height()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use crate::{paint::*, util::undoer::Undoer, *};
|
||||
use crate::{
|
||||
paint::{text::cursor::*, *},
|
||||
util::undoer::Undoer,
|
||||
*,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
|
|
Loading…
Reference in New Issue