Skip to content

Commit

Permalink
Cosmic Text (Continuation of #179) (#194)
Browse files Browse the repository at this point in the history
* implement cosmic_text as the text renderer and editor

* fix text related tests

* fix text sizing being incorrect on certain fonts

* Re-add TextAlignment so that we don't break more code

* Restore textbox API

* Fix overflow detection; left debug prints in while I diagnose other problems

* Make PaintLimits optional because they aren't initialized right away

* Fix textbox state machine

* Working textbox state machine

* Fix up modifier keys and some warnings

* Fix alignment (which works in more cases now! O_o)

* Remove debug prints, fix cursor when buffer is empty

* Use ((x, y), (w, h)) order instead of ((x, w), (y, h)) order for coordinates

* Improve variable names since there are two 'text changed' variables now

* Re-export cosmic_text from yakui crate

* Fix tests, restore old YakuiWgpu constructor, add button alignment example

* Remove Yakui argument from boostrap, app, and demo

* Always import cosmic_text from yakui instead of yakui_widgets if available

* Restore examples and text API to use (potentially flawed) Into<String> generic

* Fix clear_textbox example

* Remove yakui_widgets references from yakui_app and bootstrap

* Clean up examples

* Borrow and initialize Option instead of taking it to prevent possible mistakes

* Triple whammy to fix button alignment — I think this is good enough

* Accept snapshots, for better or worse

---------

Co-authored-by: Madeline Sparkles <[email protected]>
  • Loading branch information
LPGhatguy and msparkles authored Nov 30, 2024
1 parent 7205a74 commit 34a83da
Show file tree
Hide file tree
Showing 35 changed files with 1,691 additions and 807 deletions.
427 changes: 427 additions & 0 deletions crates/bootstrap/assets/OpenMoji-LICENSE.txt

Large diffs are not rendered by default.

Binary file not shown.
16 changes: 9 additions & 7 deletions crates/bootstrap/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod custom_texture;

use std::fmt::Write;
use std::sync::Arc;
use std::time::Instant;

use winit::{
Expand All @@ -10,11 +11,14 @@ use winit::{
};

use winit::window::{Window, WindowAttributes, WindowId};
use yakui::font::{Font, FontSettings, Fonts};
use yakui::cosmic_text::fontdb;
use yakui::font::Fonts;
use yakui::paint::{Texture, TextureFilter, TextureFormat};
use yakui::{ManagedTextureId, Rect, TextureId, UVec2, Vec2, Yakui};
use yakui_app::Graphics;

pub const OPENMOJI: &[u8] = include_bytes!("../assets/OpenMoji-color-glyf_colr_0.ttf");

const MONKEY_PNG: &[u8] = include_bytes!("../assets/monkey.png");
const MONKEY_BLURRED_PNG: &[u8] = include_bytes!("../assets/monkey-blurred.png");
const BROWN_INLAY_PNG: &[u8] = include_bytes!("../assets/brown_inlay.png");
Expand Down Expand Up @@ -232,13 +236,11 @@ fn run(body: impl ExampleBody) {

// Add a custom font for some of the examples.
let fonts = yak.dom().get_global_or_init(Fonts::default);
let font = Font::from_bytes(
include_bytes!("../assets/Hack-Regular.ttf").as_slice(),
FontSettings::default(),
)
.unwrap();

fonts.add(font, Some("monospace"));
static HACK_REGULAR: &[u8] = include_bytes!("../assets/Hack-Regular.ttf");

fonts.load_font_source(fontdb::Source::Binary(Arc::from(&HACK_REGULAR)));
fonts.set_monospace_family("Hack");

// Set up some default state that we'll modify later.
let mut app = App {
Expand Down
1 change: 0 additions & 1 deletion crates/yakui-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ edition = "2021"
[dependencies]
yakui = { path = "../yakui", version = "0.3.0" }
yakui-core = { path = "../yakui-core", version = "0.3.0" }
yakui-widgets = { path = "../yakui-widgets", version = "0.3.0", default-features = false }
yakui-winit = { path = "../yakui-winit", version = "0.3.0" }
yakui-wgpu = { path = "../yakui-wgpu", version = "0.3.0" }

Expand Down
2 changes: 1 addition & 1 deletion crates/yakui-core/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ pub enum WidgetEvent {
},

/// Text was sent to the widget.
TextInput(char),
TextInput(char, Modifiers),

/// The widget was focused or unfocused.
FocusChanged(bool),
Expand Down
2 changes: 1 addition & 1 deletion crates/yakui-core/src/input/input_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ impl InputState {
// Panic safety: if this node is in the layout DOM, it must be
// in the DOM.
let mut node = dom.get_mut(id).unwrap();
let event = WidgetEvent::TextInput(c);
let event = WidgetEvent::TextInput(c, self.modifiers.get());
return self.fire_event(dom, layout, id, &mut node, &event);
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/yakui-core/tests/regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl Widget for KeyboardWidget {
}

fn event(&mut self, _ctx: EventContext<'_>, event: &WidgetEvent) -> EventResponse {
if let WidgetEvent::TextInput(_) = event {
if let WidgetEvent::TextInput(..) = event {
self.count.fetch_add(1, Ordering::SeqCst);
}

Expand Down
8 changes: 7 additions & 1 deletion crates/yakui-vulkan/examples/demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ fn main() {
&vulkan_test.device,
vulkan_test.present_queue,
vulkan_test.device_memory_properties,
vulkan_test.device_properties,
);
let mut options = yakui_vulkan::Options::default();
options.render_pass = vulkan_test.render_pass;
let mut yakui_vulkan = YakuiVulkan::new(&vulkan_context, options);
let mut yakui_vulkan = YakuiVulkan::new(&mut yak, &vulkan_context, options);
// Prepare for one frame in flight
yakui_vulkan.transfers_submitted();
let gui_state = GuiState {
Expand Down Expand Up @@ -93,6 +94,7 @@ fn main() {
&vulkan_test.device,
vulkan_test.present_queue,
vulkan_test.device_memory_properties,
vulkan_test.device_properties,
);

yak.start();
Expand Down Expand Up @@ -232,6 +234,7 @@ struct VulkanTest {
instance: ash::Instance,
surface_loader: ash::khr::surface::Instance,
device_memory_properties: vk::PhysicalDeviceMemoryProperties,
device_properties: vk::PhysicalDeviceProperties,

present_queue: vk::Queue,

Expand Down Expand Up @@ -511,6 +514,8 @@ impl VulkanTest {
let device_memory_properties =
unsafe { instance.get_physical_device_memory_properties(physical_device) };

let device_properties = unsafe { instance.get_physical_device_properties(physical_device) };

Self {
device,
physical_device,
Expand All @@ -520,6 +525,7 @@ impl VulkanTest {
surface_loader,
swapchain_info,
device_memory_properties,
device_properties,
surface,
swapchain,
present_image_views,
Expand Down
13 changes: 10 additions & 3 deletions crates/yakui-vulkan/shaders/main.frag
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ void main() {
}

if (workflow == WORKFLOW_TEXT) {
float coverage = texture(textures[texture_id], in_uv).r;
out_color = in_color * coverage;
vec4 coverage = texture(textures[texture_id], in_uv);

if (in_color.a > 0.0) {
float alpha = max(max(coverage.r, coverage.g), coverage.b) * in_color.a * coverage.a;

out_color = vec4(in_color.rgb * alpha, alpha);
} else {
out_color = coverage;
}
} else {
vec4 user_texture = texture(textures[texture_id], in_uv);
out_color = in_color * user_texture;
}
}
}
Binary file modified crates/yakui-vulkan/shaders/main.frag.spv
Binary file not shown.
11 changes: 8 additions & 3 deletions crates/yakui-wgpu/shaders/text.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@ fn vs_main(

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let coverage = textureSample(coverage_texture, coverage_sampler, in.texcoord).r;
let alpha = coverage * in.color.a;
let coverage = textureSample(coverage_texture, coverage_sampler, in.texcoord);

return vec4(in.color.rgb * alpha, alpha);
if in.color.a > 0.0 {
let alpha = max(max(coverage.r, coverage.g), coverage.b) * in.color.a * coverage.a;

return vec4(in.color.rgb * alpha, alpha);
} else {
return coverage;
}
}
7 changes: 5 additions & 2 deletions crates/yakui-widgets/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ default-fonts = []
[dependencies]
yakui-core = { path = "../yakui-core", version = "0.3.0" }

fontdue = "0.8.0"
cosmic-text = { version = "0.12.0", default-features = false, features = [
"std",
"swash",
] }
sys-locale = "0.3.1"
thunderdome = "0.6.0"
smol_str = "0.2.1"

[dev-dependencies]
yakui = { path = "../yakui" }
Expand Down
123 changes: 47 additions & 76 deletions crates/yakui-widgets/src/font.rs
Original file line number Diff line number Diff line change
@@ -1,118 +1,89 @@
use std::cell::Ref;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::rc::Rc;

use smol_str::SmolStr;
use thunderdome::{Arena, Index};

pub use fontdue::{Font, FontSettings};
use std::sync::Arc;

#[derive(Clone)]
pub struct Fonts {
inner: Rc<RefCell<FontsInner>>,
}

struct FontsInner {
storage: Arena<Font>,
by_name: HashMap<FontName, FontId>,
font_system: cosmic_text::FontSystem,
}

impl Fonts {
#[allow(unused_mut, unused_assignments)]
fn new() -> Self {
let mut storage = Arena::new();
let mut by_name = HashMap::new();
let mut font_system = cosmic_text::FontSystem::new_with_locale_and_db(
sys_locale::get_locale().unwrap_or(String::from("en-US")),
{
let mut database = cosmic_text::fontdb::Database::default();
database.set_serif_family("");
database.set_sans_serif_family("");
database.set_cursive_family("");
database.set_fantasy_family("");
database.set_monospace_family("");
database
},
);

#[cfg(feature = "default-fonts")]
{
static DEFAULT_BYTES: &[u8] = include_bytes!("../assets/Roboto-Regular.ttf");

let font = Font::from_bytes(DEFAULT_BYTES, FontSettings::default())
.expect("failed to load built-in font");
let id = FontId::new(storage.insert(font));
by_name.insert(FontName::new("default"), id);
font_system
.db_mut()
.load_font_source(cosmic_text::fontdb::Source::Binary(Arc::from(
&DEFAULT_BYTES,
)));
}

let inner = Rc::new(RefCell::new(FontsInner { storage, by_name }));
let inner = Rc::new(RefCell::new(FontsInner { font_system }));
Self { inner }
}

pub fn add<S: Into<FontName>>(&self, font: Font, name: Option<S>) -> FontId {
let mut inner = self.inner.borrow_mut();

let id = FontId::new(inner.storage.insert(font));
if let Some(name) = name {
inner.by_name.insert(name.into(), id);
}

id
}

pub fn get(&self, name: &FontName) -> Option<Ref<'_, Font>> {
let inner = self.inner.borrow();

let &id = inner.by_name.get(name)?;
if inner.storage.contains(id.0) {
Some(Ref::map(inner, |inner| inner.storage.get(id.0).unwrap()))
} else {
None
}
}
}
pub fn with_system<T>(&self, f: impl FnOnce(&mut cosmic_text::FontSystem) -> T) -> T {
let mut inner = (*self.inner).borrow_mut();

impl Default for Fonts {
fn default() -> Self {
Self::new()
f(&mut inner.font_system)
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FontName(SmolStr);

impl FontName {
pub fn new<S: AsRef<str>>(name: S) -> Self {
Self(name.as_ref().into())
pub fn load_font_source(
&self,
source: cosmic_text::fontdb::Source,
) -> Vec<cosmic_text::fontdb::ID> {
self.with_system(|font_system| font_system.db_mut().load_font_source(source))
.to_vec()
}

pub fn as_str(&self) -> &str {
self.0.as_str()
/// Sets the family that will be used by `Family::Serif`.
pub fn set_serif_family<S: Into<String>>(&self, family: S) {
self.with_system(|font_system| font_system.db_mut().set_serif_family(family));
}
}

impl From<&str> for FontName {
fn from(value: &str) -> Self {
Self(value.into())
/// Sets the family that will be used by `Family::SansSerif`.
pub fn set_sans_serif_family<S: Into<String>>(&self, family: S) {
self.with_system(|font_system| font_system.db_mut().set_sans_serif_family(family));
}
}

impl From<&String> for FontName {
fn from(value: &String) -> Self {
Self(value.into())
/// Sets the family that will be used by `Family::Cursive`.
pub fn set_cursive_family<S: Into<String>>(&self, family: S) {
self.with_system(|font_system| font_system.db_mut().set_cursive_family(family));
}
}

impl fmt::Display for FontName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
/// Sets the family that will be used by `Family::Fantasy`.
pub fn set_fantasy_family<S: Into<String>>(&self, family: S) {
self.with_system(|font_system| font_system.db_mut().set_fantasy_family(family));
}
}

/// Identifies a font that has been loaded and can be used by yakui.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct FontId(Index);

impl FontId {
#[inline]
pub(crate) fn new(index: Index) -> Self {
Self(index)
/// Sets the family that will be used by `Family::Monospace`.
pub fn set_monospace_family<S: Into<String>>(&self, family: S) {
self.with_system(|font_system| font_system.db_mut().set_monospace_family(family));
}
}

impl fmt::Debug for FontId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FontId({}, {})", self.0.slot(), self.0.generation())
impl Default for Fonts {
fn default() -> Self {
Self::new()
}
}
2 changes: 2 additions & 0 deletions crates/yakui-widgets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pub mod widgets;

pub use self::shorthand::*;

pub use cosmic_text;

#[doc(hidden)]
pub struct DocTest {
state: yakui_core::Yakui,
Expand Down
4 changes: 2 additions & 2 deletions crates/yakui-widgets/src/shapes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ pub fn cross(output: &mut PaintDom, rect: Rect, color: Color) {
output.add_mesh(mesh);
}

pub fn selection_halo(output: &mut PaintDom, rect: Rect) {
outline(output, rect, 2.0, Color::WHITE);
pub fn selection_halo(output: &mut PaintDom, rect: Rect, color: Color) {
outline(output, rect, 2.0, color);
}

pub fn outline(output: &mut PaintDom, rect: Rect, w: f32, color: Color) {
Expand Down
Loading

0 comments on commit 34a83da

Please sign in to comment.