Text library moved from libcosmic

This commit is contained in:
Jeremy Soller 2022-10-18 12:07:22 -06:00
commit 410d4ee674
No known key found for this signature in database
GPG key ID: 87F211AF2BE4C2FE
37 changed files with 12909 additions and 0 deletions

View file

@ -0,0 +1,139 @@
use cosmic::{
iced::widget::{
container,
},
iced::{
self,
Application,
Command,
Element,
Theme
},
settings,
};
use cosmic_text::{
FontMatches,
FontSystem,
TextBuffer,
};
use std::{
env,
fs,
sync::{Arc, RwLock},
};
use self::text_box::text_box;
mod text_box;
lazy_static::lazy_static! {
static ref FONT_SYSTEM: FontSystem = FontSystem::new();
}
//TODO: find out how to do this!
static mut FONT_MATCHES: Option<FontMatches<'static>> = None;
fn main() -> cosmic::iced::Result {
env_logger::init();
let font_matches: FontMatches<'static> = FONT_SYSTEM.matches(|info| -> bool {
#[cfg(feature = "mono")]
let monospaced = true;
#[cfg(not(feature = "mono"))]
let monospaced = false;
let matched = {
info.style == fontdb::Style::Normal &&
info.weight == fontdb::Weight::NORMAL &&
info.stretch == fontdb::Stretch::Normal &&
(info.monospaced == monospaced || info.post_script_name.contains("Emoji"))
};
if matched {
log::debug!(
"{:?}: family '{}' postscript name '{}' style {:?} weight {:?} stretch {:?} monospaced {:?}",
info.id,
info.family,
info.post_script_name,
info.style,
info.weight,
info.stretch,
info.monospaced
);
}
matched
}).unwrap();
unsafe { FONT_MATCHES = Some(font_matches); }
let mut settings = settings();
settings.window.min_size = Some((400, 100));
Window::run(settings)
}
pub struct Window {
buffer: Arc<RwLock<TextBuffer<'static>>>,
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub enum Message {}
impl Application for Window {
type Executor = iced::executor::Default;
type Flags = ();
type Message = Message;
type Theme = Theme;
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
let font_sizes = [
(10, 14), // Caption
(14, 20), // Body
(20, 28), // Title 4
(24, 32), // Title 3
(28, 36), // Title 2
(32, 44), // Title 1
];
let font_size_default = 1; // Body
let mut font_size_i = font_size_default;
let text = if let Some(arg) = env::args().nth(1) {
fs::read_to_string(&arg).expect("failed to open file")
} else {
#[cfg(feature = "mono")]
let default_text = include_str!("../../../sample/mono.txt");
#[cfg(not(feature = "mono"))]
let default_text = include_str!("../../../sample/proportional.txt");
default_text.to_string()
};
let line_x = 8;
let buffer = Arc::new(RwLock::new(TextBuffer::new(
unsafe { FONT_MATCHES.as_ref().unwrap() },
&text,
font_sizes[font_size_i].0,
600 - line_x * 2,
)));
let window = Window {
buffer,
};
(window, Command::none())
}
fn title(&self) -> String {
let buffer = self.buffer.read().unwrap();
format!("COSMIC Text - iced - {}", buffer.font_matches().locale)
}
fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
Command::none()
}
fn view(&self) -> Element<Message> {
container(
text_box(self.buffer.clone())
).padding(16).into()
}
}

View file

@ -0,0 +1,272 @@
use cosmic::iced_native::{
{Color, Element, Length, Point, Rectangle, Size, Shell},
clipboard::Clipboard,
event::{
Event,
Status,
},
keyboard::{Event as KeyEvent, KeyCode},
layout::{self, Layout},
renderer,
widget::{self, Widget},
};
use cosmic_text::{
FontLineIndex,
TextAction,
TextBuffer,
};
use std::{
sync::{Arc, RwLock},
time::Instant,
};
pub struct TextBox<'a> {
buffer: Arc<RwLock<TextBuffer<'a>>>,
}
impl<'a> TextBox<'a> {
pub fn new(buffer: Arc<RwLock<TextBuffer<'a>>>) -> Self {
Self { buffer }
}
}
pub fn text_box<'a>(buffer: Arc<RwLock<TextBuffer<'a>>>) -> TextBox<'a> {
TextBox::new(buffer)
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for TextBox<'a>
where
Renderer: renderer::Renderer,
{
fn width(&self) -> Length {
Length::Shrink
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
println!("{:?}", limits);
let size = limits.max();
{
let mut buffer = self.buffer.write().unwrap();
let font_size = buffer.font_size();
let line_height = font_size + 8;
buffer.set_line_width(size.width as i32);
buffer.shape_until(size.height as i32 / line_height);
}
layout::Node::new(size)
}
fn draw(
&self,
_state: &widget::Tree,
renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
let buffer = self.buffer.read().unwrap();
let font_size = buffer.font_size();
let line_height = font_size + 8; /*TODO: store somewhere else */
let scroll = 0;
let instant = Instant::now();
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color::from_rgb8(0x34, 0x34, 0x34),
);
let line_x = layout.bounds().x as i32;
let mut line_y = layout.bounds().y as i32 + line_height;
let mut start_line_opt = None;
let mut end_line = FontLineIndex::new(0);
for (line_i, line) in buffer
.layout_lines()
.iter()
.skip(scroll as usize)
.enumerate()
{
if line_y >= (layout.bounds().y + layout.bounds().height) as i32 {
break;
}
end_line = line.line_i;
if start_line_opt == None {
start_line_opt = Some(end_line);
}
if buffer.cursor.line == line_i + scroll as usize {
if buffer.cursor.glyph >= line.glyphs.len() {
let x = match line.glyphs.last() {
Some(glyph) => glyph.x + glyph.w,
None => 0.0,
};
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle::new(
Point::new(line_x as f32 + x, (line_y - font_size) as f32),
Size::new((font_size / 2) as f32, line_height as f32)
),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.125),
);
} else {
let glyph = &line.glyphs[buffer.cursor.glyph];
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle::new(
Point::new(line_x as f32 + glyph.x, (line_y - font_size) as f32),
Size::new(glyph.w, line_height as f32)
),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.125),
);
let text_line = &buffer.text_lines()[line.line_i.get()];
log::info!(
"{}, {}: '{}' ('{}'): '{}'",
glyph.start,
glyph.end,
glyph.font.info.family,
glyph.font.info.post_script_name,
&text_line[glyph.start..glyph.end],
);
}
}
line.draw(0xFFFFFF, |x, y, data| {
let a = (data >> 24) as u8;
if a > 0 {
let r = (data >> 16) as u8;
let g = (data >> 8) as u8;
let b = data as u8;
let bounds = Rectangle::new(
Point::new((line_x + x) as f32, (line_y + y) as f32),
Size::new(1.0, 1.0)
);
let color = Color::from_rgba8(r, g, b, a as f32 / 255.0);
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
color
);
}
});
line_y += line_height;
}
/*
// Draw scrollbar
{
let start_line = start_line_opt.unwrap_or(end_line);
let lines = buffer.text_lines().len();
let start_y = (start_line.get() * window.height() as usize) / lines;
let end_y = (end_line.get() * window.height() as usize) / lines;
if end_y > start_y {
window.rect(
window.width() as i32 - line_x as i32,
start_y as i32,
line_x as u32,
(end_y - start_y) as u32,
Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.25),
);
}
}
buffer.redraw = false;
*/
let duration = instant.elapsed();
log::debug!("redraw: {:?}", duration);
}
fn on_event(
&mut self,
_state: &mut widget::Tree,
event: Event,
_layout: Layout<'_>,
_cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
) -> Status {
let mut buffer = self.buffer.write().unwrap();
match event {
Event::Keyboard(key_event) => match key_event {
KeyEvent::KeyPressed { key_code, modifiers } => {
match key_code {
KeyCode::Left => {
buffer.action(TextAction::Left);
Status::Captured
},
KeyCode::Right => {
buffer.action(TextAction::Right);
Status::Captured
},
KeyCode::Up => {
buffer.action(TextAction::Up);
Status::Captured
},
KeyCode::Down => {
buffer.action(TextAction::Down);
Status::Captured
},
KeyCode::Backspace => {
buffer.action(TextAction::Backspace);
Status::Captured
},
KeyCode::Delete => {
buffer.action(TextAction::Delete);
Status::Captured
},
_ => Status::Ignored,
}
},
KeyEvent::CharacterReceived(character) => {
buffer.action(TextAction::Insert(character));
Status::Captured
},
_ => Status::Ignored,
},
_ => Status::Ignored,
}
}
}
impl<'a, Message, Renderer> From<TextBox<'a>> for Element<'a, Message, Renderer>
where
Renderer: renderer::Renderer,
{
fn from(text_box: TextBox<'a>) -> Self {
Self::new(text_box)
}
}