Update cosmic-jotdown
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
Isaac Mills 2024-04-19 11:20:13 -04:00
parent 11bbb2500f
commit a7f3c12018
Signed by: fnmain
GPG key ID: B67D7410F33A0F61
5 changed files with 460 additions and 498 deletions

482
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -7,8 +7,10 @@ rust-version = "1.72"
[dependencies] [dependencies]
cosmic-jotdown = { git = "https://git.nations.lol/fnmain/cosmic-jotdown" } # cosmic-jotdown = { git = "https://git.nations.lol/fnmain/cosmic-jotdown" }
eframe = { version = "0.26.2", default-features = false, features = [ cosmic-jotdown = {path = "../cosmic-jotdown"}
jotdown = { git = "https://git.nations.lol/fnmain/jotdown" }
eframe = { version = "0.27.2", default-features = false, features = [
"accesskit", # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies. "accesskit", # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies.
# "default_fonts", # "default_fonts",
"wgpu", # Use the glow rendering backend. Alternative: "wgpu". "wgpu", # Use the glow rendering backend. Alternative: "wgpu".
@ -16,17 +18,17 @@ eframe = { version = "0.26.2", default-features = false, features = [
"wayland", "wayland",
] } ] }
egui-glyphon = { git = "https://git.nations.lol/fnmain/egui-glyphon" } egui-glyphon = { git = "https://git.nations.lol/fnmain/egui-glyphon" }
egui_extras = { version = "0.26.2", features = ["image", "http", "file"] } egui_extras = { version = "0.27.2", features = ["image", "http", "file"] }
encase = { version = "0.7.0", features = ["glam"] } encase = { version = "0.7.0", features = ["glam"] }
glam = "0.25.0" glam = "0.25.0"
image = { version = "0.24.9", features = ["jpeg", "png"] } image = { version = "0.24.9", features = ["jpeg", "png"], defaut-features = false }
keyframe = { version = "1.1.1", default-features = false } keyframe = { version = "1.1.1", default-features = false }
log = "0.4" log = "0.4"
range-map = "0.2.0" rangemap = "1.5.1"
# native: # native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
env_logger = "0.10" env_logger = "0.11"
# web: # web:
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 MiB

After

Width:  |  Height:  |  Size: 3.7 MiB

View file

@ -12,4 +12,4 @@
- I balance the time I spend at my computer with outdoor activities like, camping, hiking, and backpacking - I balance the time I spend at my computer with outdoor activities like, camping, hiking, and backpacking
- I know how to both write code for computers, as well as whip them into shape when they start having problems. I am the IT guy in my family - I know how to both write code for computers, as well as whip them into shape when they start having problems. I am the IT guy in my family
\*Special thanks to _meico_ from Shadertoy for the GLSL version of your Spectrum Ring shader \*Special thanks to _meico_ from Shadertoy for the GLSL version of your [Spectrum Ring shader](https://www.shadertoy.com/view/XssXWH)

View file

@ -13,12 +13,11 @@ use std::sync::Arc;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use arc_swap::ArcSwapAny; use arc_swap::ArcSwapAny;
use cosmic_jotdown::jotdown::{self, Event, ListKind}; use cosmic_jotdown::Indent;
use cosmic_jotdown::{Indent, INDENT_AMOUNT};
use eframe::egui::mutex::{Mutex, RwLock}; use eframe::egui::mutex::{Mutex, RwLock};
use eframe::egui::{ use eframe::egui::{
self, lerp, Align2, Id, Image, ImageSize, ImageSource, LayerId, OpenUrl, Pos2, Rect, Rounding, self, lerp, Align2, Id, Image, ImageSize, ImageSource, LayerId, OpenUrl, Pos2, Rect, Rounding,
Sense, Stroke, Vec2, Sense, Vec2,
}; };
use eframe::egui_wgpu::{self, wgpu}; use eframe::egui_wgpu::{self, wgpu};
use egui::{Color32, Frame}; use egui::{Color32, Frame};
@ -28,8 +27,9 @@ use egui_glyphon::{BufferWithTextArea, GlyphonRenderer, GlyphonRendererCallback}
use encase::ShaderType; use encase::ShaderType;
use glam::Mat2; use glam::Mat2;
use glyphon::{Buffer, FontSystem, Metrics}; use glyphon::{Buffer, FontSystem, Metrics};
use jotdown::Event;
use keyframe::functions; use keyframe::functions;
use range_map::RangeMap; use rangemap::RangeMap;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
/// We derive Deserialize/Serialize so we can persist app state on shutdown. /// We derive Deserialize/Serialize so we can persist app state on shutdown.
@ -68,7 +68,7 @@ pub enum State {
#[derive(Default)] #[derive(Default)]
pub struct ContextWindow { pub struct ContextWindow {
pub size: Vec2, pub size: Vec2,
pub text: Vec<(Rect, Indent, ContextBlock)>, pub text: Vec<ResolvedItem<'static>>,
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub name: &'static str, pub name: &'static str,
} }
@ -80,16 +80,6 @@ pub struct ContextIcon {
pub name: &'static str, pub name: &'static str,
} }
#[derive(Clone)]
pub enum ContextBlock {
Buffer(Arc<RwLock<Buffer>>, Option<RangeMap<usize, &'static str>>),
Image {
alt_text: Arc<RwLock<Buffer>>,
image: Image<'static>,
hi_image: Image<'static>,
},
}
const CONTEXT_METRICS: Metrics = Metrics::new(16.0, 18.0); const CONTEXT_METRICS: Metrics = Metrics::new(16.0, 18.0);
const HOVER_TIME: f64 = 0.5; const HOVER_TIME: f64 = 0.5;
@ -231,6 +221,14 @@ const NAME_PLATE: [&str; 3] = [
"My portfolio", "My portfolio",
]; ];
pub struct ResolvedItem<'a> {
pub indent: Indent,
pub buffer: Arc<RwLock<Buffer>>,
pub relative_bounds: Rect,
pub url_map: Option<RangeMap<usize, Cow<'a, str>>>,
pub image_urls: Option<(String, String)>,
}
impl ContextWindow { impl ContextWindow {
pub fn set_content( pub fn set_content(
&mut self, &mut self,
@ -238,119 +236,34 @@ impl ContextWindow {
#[cfg(target_arch = "wasm32")] transform: Option<Mat2>, #[cfg(target_arch = "wasm32")] transform: Option<Mat2>,
#[cfg(target_arch = "wasm32")] name: Option<&'static str>, #[cfg(target_arch = "wasm32")] name: Option<&'static str>,
font_system: &mut FontSystem, font_system: &mut FontSystem,
mut max_width: f32, max_width: f32,
#[cfg(target_arch = "wasm32")] states: &mut Vec<State>, #[cfg(target_arch = "wasm32")] states: &mut Vec<State>,
) { ) {
self.size = Vec2::new(max_width / 1.5, 0.0); self.size = Vec2::new(max_width / 1.5, 0.0);
let mut last_indent = None; let text =
let mut last_image_size: Option<Vec2> = None; cosmic_jotdown::jotdown_into_buffers(content.iter().cloned()).collect::<Vec<_>>();
max_width /= 1.5;
self.text = cosmic_jotdown::jotdown_into_buffers( let (size, text) = cosmic_jotdown::resolve_paragraphs(
content.iter().cloned(), &text,
self.size,
font_system, font_system,
CONTEXT_METRICS, CONTEXT_METRICS,
max_width, None,
) 1.0,
.map(|buffer| { env!("PHOST"),
let measurement = measure_buffer(&buffer.buffer, self.size);
let paragraph_height = if last_indent.is_none() || buffer.indent.modifier.is_none() {
CONTEXT_METRICS.line_height * 1.5
} else {
8.0
};
last_indent = buffer.indent.modifier;
let buffer = if let Some(url) = buffer.image_url {
let image;
let hi_image;
let url = url.split_once('#').unwrap();
let size = url.1.split_once('x').unwrap();
let size = Vec2::new(size.0.parse().unwrap(), size.1.parse().unwrap());
#[cfg(target_arch = "wasm32")]
{
image = Image::from_uri(format!(concat!(env!("PHOST"), "/{}"), url.0));
let split = url.0.rsplit_once('.').unwrap();
hi_image = Image::from_uri(format!(
concat!(env!("PHOST"), "/{}_hi.{}"),
split.0, split.1
));
}
#[cfg(not(target_arch = "wasm32"))]
{
image = Image::from_uri(format!("file://assets/{}", url.0));
let split = url.0.rsplit_once('.').unwrap();
hi_image = Image::from_uri(format!("file://assets/{}_hi.{}", split.0, split.1));
}
let mut res = (
Rect::from_min_size(
Pos2::new(buffer.indent.indent, self.size.y + paragraph_height),
size,
),
buffer.indent,
ContextBlock::Image {
alt_text: Arc::new(RwLock::new(buffer.buffer)),
image,
hi_image,
},
); );
const IMAGE_PADDING: f32 = 8.0;
if let Some(last_size) = last_image_size.as_mut() {
let ls = *last_size;
last_size.x += size.x + IMAGE_PADDING;
if last_size.x > max_width { self.text = text
self.size.y += last_size.y + paragraph_height; .into_iter()
last_size.x = size.x + IMAGE_PADDING; .map(|t| ResolvedItem {
last_size.y = size.y; indent: t.indent,
res.0 = Rect::from_min_size( buffer: Arc::new(RwLock::new(t.buffer)),
Pos2::new(buffer.indent.indent, self.size.y + paragraph_height), relative_bounds: t.relative_bounds,
size, url_map: t.url_map,
); image_urls: t.image_urls,
} else {
last_size.y = last_size.y.max(size.y);
res.0 = res.0.translate(Vec2::new(ls.x, 0.0));
}
} else {
if size.x > max_width {
let max_size = Vec2::new(max_width, size.y);
let new_size = ImageSize {
max_size,
..Default::default()
}
.calc_size(max_size, size);
res.0 = Rect::from_min_size(
Pos2::new(buffer.indent.indent, self.size.y + paragraph_height),
new_size,
);
self.size.y += new_size.y + paragraph_height;
} else {
last_image_size = Some(size + Vec2::new(IMAGE_PADDING, 0.0));
}
}
res
} else {
if let Some(size) = last_image_size {
self.size.y += size.y + paragraph_height;
}
let res = (
Rect::from_min_size(
Pos2::new(buffer.indent.indent, self.size.y + paragraph_height),
measurement.size(),
),
buffer.indent,
ContextBlock::Buffer(Arc::new(RwLock::new(buffer.buffer)), buffer.url_map),
);
last_image_size = None;
self.size.y += measurement.height() + paragraph_height;
res
};
self.size.x = self.size.x.max(measurement.width());
buffer
}) })
.collect(); .collect();
self.size = size;
if let Some(size) = last_image_size {
self.size.y += size.y;
}
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
if let Some(name) = name { if let Some(name) = name {
@ -743,113 +656,19 @@ impl eframe::App for Portfolio {
Rounding::same(25.0), Rounding::same(25.0),
Color32::BLACK.gamma_multiply(lerp(0.0..=0.5, zoom_view_opacity)), Color32::BLACK.gamma_multiply(lerp(0.0..=0.5, zoom_view_opacity)),
); );
self.window self.window.text.iter().for_each(|context_block| {
.text if let Some(image_urls) = &context_block.image_urls {
.iter() let image_rect =
.for_each(|context_block| match &context_block.2 { context_block.relative_bounds.translate(rect.min.to_vec2());
ContextBlock::Buffer(buffer, url_map) => {
let text_rect = context_block.0.translate(rect.min.to_vec2());
// ui.painter().debug_rect(text_rect, Color32::GREEN, "");
if let Some(url_map) = url_map {
let text_response = ui.allocate_rect(text_rect, Sense::click());
let mut buffer = buffer.write();
let mut editor =
Editor::new(BufferRef::Borrowed(buffer.deref_mut()));
if text_response.hovered()
&& ui.input(|i| i.raw_scroll_delta == Vec2::ZERO)
{
let mouse_pos = ui
.input(|i| i.pointer.latest_pos().unwrap_or_default())
- text_rect.min.to_vec2();
editor.action(
self.font_system.lock().deref_mut(),
glyphon::Action::Click {
x: mouse_pos.x as i32,
y: mouse_pos.y as i32 - 3,
},
);
let mut location = editor.cursor();
match location.affinity {
glyphon::Affinity::After => location.index += 1,
glyphon::Affinity::Before => {}
}
if url_map.get(location.index).is_some() {
ctx.set_cursor_icon(egui::CursorIcon::PointingHand);
}
}
if text_response.clicked() && !self.image_zoomed {
let mouse_click = ui.input(|i| {
i.pointer.interact_pos().unwrap_or_default()
}) - text_rect.min.to_vec2();
editor.action(
self.font_system.lock().deref_mut(),
glyphon::Action::Click {
x: mouse_click.x as i32,
y: mouse_click.y as i32,
},
);
let mut location = editor.cursor();
match location.affinity {
glyphon::Affinity::After => location.index += 1,
glyphon::Affinity::Before => {}
}
if let Some(url) = url_map.get(location.index) {
link_clicked = true;
if url.starts_with('#') {
if let Some(icon) = CONTEXT_ICONS
.iter()
.find(|i| i.name == &url[1..])
{
icon_link = Some(icon);
}
} else {
ui.ctx().open_url(OpenUrl::new_tab(url));
}
// clicked = false;
}
}
}
buffers.push(BufferWithTextArea::new(
buffer.clone(),
text_rect,
zoom_view_opacity,
Color::rgb(255, 255, 255),
ui.ctx(),
));
match context_block.1.modifier {
Some(ListKind::Unordered) => {
ui.painter().circle(
text_rect.min
+ Vec2::new(
-INDENT_AMOUNT,
CONTEXT_METRICS.line_height / 2.0,
),
2.5,
Color32::WHITE.gamma_multiply(zoom_view_opacity),
Stroke::NONE,
);
}
_ => {}
}
}
ContextBlock::Image {
image, hi_image, ..
} => {
let image_rect = context_block.0.translate(rect.min.to_vec2());
let image_response = ui.allocate_rect(image_rect, Sense::click()); let image_response = ui.allocate_rect(image_rect, Sense::click());
let image_response_clicked = image_response.clicked();
let time_offset = ui.memory_mut(|m| { let time_offset = ui.memory_mut(|m| {
let time_offset = let time_offset =
m.data.get_temp_mut_or_default::<f64>(image_response.id); m.data.get_temp_mut_or_default::<f64>(image_response.id);
if image_response.clicked() && !self.image_zoomed && self.zoomed if image_response_clicked && !self.image_zoomed && self.zoomed {
{
link_clicked = true; link_clicked = true;
*time_offset = time; *time_offset = time;
self.last_image_zoomed = image_response.id; self.last_image_zoomed = image_response.id;
@ -895,27 +714,24 @@ impl eframe::App for Portfolio {
ui.painter().rect_filled( ui.painter().rect_filled(
ui.max_rect(), ui.max_rect(),
Rounding::default(), Rounding::default(),
Color32::BLACK.gamma_multiply( Color32::BLACK.gamma_multiply(keyframe::ease_with_scaled_time(
keyframe::ease_with_scaled_time(
functions::EaseInOutCubic, functions::EaseInOutCubic,
if self.image_zoomed { 0.0 } else { 0.6 }, if self.image_zoomed { 0.0 } else { 0.6 },
if self.image_zoomed { 0.6 } else { 0.0 }, if self.image_zoomed { 0.6 } else { 0.0 },
time - time_offset, time - time_offset,
0.5, 0.5,
), )),
),
); );
if t > 0.0 { if t > 0.0 {
hi_image.clone() Image::from_uri(&image_urls.1)
} else { } else {
image.clone() Image::from_uri(&image_urls.0)
} }
.tint(Color32::WHITE.gamma_multiply(zoom_view_opacity)) .tint(Color32::WHITE.gamma_multiply(zoom_view_opacity))
.paint_at(&ui, image_rect.lerp_towards(&fs_rect, t)); .paint_at(&ui, image_rect.lerp_towards(&fs_rect, t));
} else { } else {
image Image::from_uri(&image_urls.0)
.clone()
.tint(Color32::WHITE.gamma_multiply(zoom_view_opacity)) .tint(Color32::WHITE.gamma_multiply(zoom_view_opacity))
.paint_at( .paint_at(
ui, ui,
@ -931,6 +747,80 @@ impl eframe::App for Portfolio {
), ),
); );
} }
} else {
let text_rect =
context_block.relative_bounds.translate(rect.min.to_vec2());
// ui.painter().debug_rect(text_rect, Color32::GREEN, "");
if let Some(url_map) = &context_block.url_map {
let text_response = ui.allocate_rect(text_rect, Sense::click());
let mut buffer = context_block.buffer.write();
let mut editor =
Editor::new(BufferRef::Borrowed(buffer.deref_mut()));
if text_response.hovered()
&& ui.input(|i| i.raw_scroll_delta == Vec2::ZERO)
{
let mouse_pos = ui
.input(|i| i.pointer.latest_pos().unwrap_or_default())
- text_rect.min.to_vec2();
editor.action(
self.font_system.lock().deref_mut(),
glyphon::Action::Click {
x: mouse_pos.x as i32,
y: mouse_pos.y as i32 - 3,
},
);
let mut location = editor.cursor();
match location.affinity {
glyphon::Affinity::After => location.index += 1,
glyphon::Affinity::Before => {}
}
if url_map.get(&location.index).is_some() {
ctx.set_cursor_icon(egui::CursorIcon::PointingHand);
}
}
if text_response.clicked() && !self.image_zoomed {
let mouse_click = ui
.input(|i| i.pointer.interact_pos().unwrap_or_default())
- text_rect.min.to_vec2();
editor.action(
self.font_system.lock().deref_mut(),
glyphon::Action::Click {
x: mouse_click.x as i32,
y: mouse_click.y as i32,
},
);
let mut location = editor.cursor();
match location.affinity {
glyphon::Affinity::After => location.index += 1,
glyphon::Affinity::Before => {}
}
if let Some(url) = url_map.get(&location.index) {
link_clicked = true;
if url.starts_with('#') {
if let Some(icon) =
CONTEXT_ICONS.iter().find(|i| i.name == &url[1..])
{
icon_link = Some(icon);
}
} else {
ui.ctx().open_url(OpenUrl::new_tab(url));
}
// clicked = false;
}
}
}
buffers.push(BufferWithTextArea::new(
context_block.buffer.clone(),
text_rect,
zoom_view_opacity,
Color::rgb(255, 255, 255),
ui.ctx(),
));
} }
}); });
} }