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]
cosmic-jotdown = { git = "https://git.nations.lol/fnmain/cosmic-jotdown" }
eframe = { version = "0.26.2", default-features = false, features = [
# cosmic-jotdown = { git = "https://git.nations.lol/fnmain/cosmic-jotdown" }
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.
# "default_fonts",
"wgpu", # Use the glow rendering backend. Alternative: "wgpu".
@ -16,17 +18,17 @@ eframe = { version = "0.26.2", default-features = false, features = [
"wayland",
] }
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"] }
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 }
log = "0.4"
range-map = "0.2.0"
rangemap = "1.5.1"
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
env_logger = "0.10"
env_logger = "0.11"
# web:
[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 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")]
use arc_swap::ArcSwapAny;
use cosmic_jotdown::jotdown::{self, Event, ListKind};
use cosmic_jotdown::{Indent, INDENT_AMOUNT};
use cosmic_jotdown::Indent;
use eframe::egui::mutex::{Mutex, RwLock};
use eframe::egui::{
self, lerp, Align2, Id, Image, ImageSize, ImageSource, LayerId, OpenUrl, Pos2, Rect, Rounding,
Sense, Stroke, Vec2,
Sense, Vec2,
};
use eframe::egui_wgpu::{self, wgpu};
use egui::{Color32, Frame};
@ -28,8 +27,9 @@ use egui_glyphon::{BufferWithTextArea, GlyphonRenderer, GlyphonRendererCallback}
use encase::ShaderType;
use glam::Mat2;
use glyphon::{Buffer, FontSystem, Metrics};
use jotdown::Event;
use keyframe::functions;
use range_map::RangeMap;
use rangemap::RangeMap;
use wgpu::util::DeviceExt;
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
@ -68,7 +68,7 @@ pub enum State {
#[derive(Default)]
pub struct ContextWindow {
pub size: Vec2,
pub text: Vec<(Rect, Indent, ContextBlock)>,
pub text: Vec<ResolvedItem<'static>>,
#[cfg(target_arch = "wasm32")]
pub name: &'static str,
}
@ -80,16 +80,6 @@ pub struct ContextIcon {
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 HOVER_TIME: f64 = 0.5;
@ -231,6 +221,14 @@ const NAME_PLATE: [&str; 3] = [
"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 {
pub fn set_content(
&mut self,
@ -238,119 +236,34 @@ impl ContextWindow {
#[cfg(target_arch = "wasm32")] transform: Option<Mat2>,
#[cfg(target_arch = "wasm32")] name: Option<&'static str>,
font_system: &mut FontSystem,
mut max_width: f32,
max_width: f32,
#[cfg(target_arch = "wasm32")] states: &mut Vec<State>,
) {
self.size = Vec2::new(max_width / 1.5, 0.0);
let mut last_indent = None;
let mut last_image_size: Option<Vec2> = None;
max_width /= 1.5;
self.text = cosmic_jotdown::jotdown_into_buffers(
content.iter().cloned(),
let text =
cosmic_jotdown::jotdown_into_buffers(content.iter().cloned()).collect::<Vec<_>>();
let (size, text) = cosmic_jotdown::resolve_paragraphs(
&text,
self.size,
font_system,
CONTEXT_METRICS,
max_width,
)
.map(|buffer| {
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;
None,
1.0,
env!("PHOST"),
);
if last_size.x > max_width {
self.size.y += last_size.y + paragraph_height;
last_size.x = size.x + IMAGE_PADDING;
last_size.y = size.y;
res.0 = Rect::from_min_size(
Pos2::new(buffer.indent.indent, self.size.y + paragraph_height),
size,
);
} 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();
if let Some(size) = last_image_size {
self.size.y += size.y;
}
self.text = text
.into_iter()
.map(|t| ResolvedItem {
indent: t.indent,
buffer: Arc::new(RwLock::new(t.buffer)),
relative_bounds: t.relative_bounds,
url_map: t.url_map,
image_urls: t.image_urls,
})
.collect();
self.size = size;
#[cfg(target_arch = "wasm32")]
if let Some(name) = name {
@ -743,196 +656,173 @@ impl eframe::App for Portfolio {
Rounding::same(25.0),
Color32::BLACK.gamma_multiply(lerp(0.0..=0.5, zoom_view_opacity)),
);
self.window
.text
.iter()
.for_each(|context_block| match &context_block.2 {
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();
self.window.text.iter().for_each(|context_block| {
if let Some(image_urls) = &context_block.image_urls {
let image_rect =
context_block.relative_bounds.translate(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 image_response = ui.allocate_rect(image_rect, Sense::click());
let image_response_clicked = image_response.clicked();
let mut location = editor.cursor();
match location.affinity {
glyphon::Affinity::After => location.index += 1,
glyphon::Affinity::Before => {}
}
let time_offset = ui.memory_mut(|m| {
let time_offset =
m.data.get_temp_mut_or_default::<f64>(image_response.id);
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,
);
}
_ => {}
if image_response_clicked && !self.image_zoomed && self.zoomed {
link_clicked = true;
*time_offset = time;
self.last_image_zoomed = image_response.id;
self.image_zoomed = true;
}
*time_offset
});
if image_response.hovered() && !self.image_zoomed {
ui.ctx().set_cursor_icon(egui::CursorIcon::ZoomIn);
}
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 time_offset = ui.memory_mut(|m| {
let time_offset =
m.data.get_temp_mut_or_default::<f64>(image_response.id);
if image_response.clicked() && !self.image_zoomed && self.zoomed
{
link_clicked = true;
*time_offset = time;
self.last_image_zoomed = image_response.id;
self.image_zoomed = true;
let fs_rect = Align2::CENTER_CENTER.align_size_within_rect(
{
let max_size = self.max_size.shrink(16.0).size();
ImageSize {
max_size,
..Default::default()
}
.calc_size(max_size, image_rect.size())
},
self.max_size,
);
*time_offset
});
if image_response.hovered() && !self.image_zoomed {
ui.ctx().set_cursor_icon(egui::CursorIcon::ZoomIn);
}
let fs_rect = Align2::CENTER_CENTER.align_size_within_rect(
{
let max_size = self.max_size.shrink(16.0).size();
ImageSize {
max_size,
..Default::default()
}
.calc_size(max_size, image_rect.size())
},
if self.last_image_zoomed == image_response.id {
let ui = egui::Ui::new(
ui.ctx().clone(),
LayerId::debug(),
Id::new("image_zoom"),
self.max_size,
self.max_size,
);
if self.last_image_zoomed == image_response.id {
let ui = egui::Ui::new(
ui.ctx().clone(),
LayerId::debug(),
Id::new("image_zoom"),
self.max_size,
self.max_size,
);
let t = keyframe::ease_with_scaled_time(
functions::EaseInOutCubic,
if self.image_zoomed { 0.0 } else { 1.0 },
if self.image_zoomed { 1.0 } else { 0.0 },
time - time_offset,
0.5,
);
let t = keyframe::ease_with_scaled_time(
ui.painter().rect_filled(
ui.max_rect(),
Rounding::default(),
Color32::BLACK.gamma_multiply(keyframe::ease_with_scaled_time(
functions::EaseInOutCubic,
if self.image_zoomed { 0.0 } else { 1.0 },
if self.image_zoomed { 1.0 } else { 0.0 },
if self.image_zoomed { 0.0 } else { 0.6 },
if self.image_zoomed { 0.6 } else { 0.0 },
time - time_offset,
0.5,
);
)),
);
ui.painter().rect_filled(
ui.max_rect(),
Rounding::default(),
Color32::BLACK.gamma_multiply(
if t > 0.0 {
Image::from_uri(&image_urls.1)
} else {
Image::from_uri(&image_urls.0)
}
.tint(Color32::WHITE.gamma_multiply(zoom_view_opacity))
.paint_at(&ui, image_rect.lerp_towards(&fs_rect, t));
} else {
Image::from_uri(&image_urls.0)
.tint(Color32::WHITE.gamma_multiply(zoom_view_opacity))
.paint_at(
ui,
image_rect.lerp_towards(
&fs_rect,
keyframe::ease_with_scaled_time(
functions::EaseInOutCubic,
if self.image_zoomed { 0.0 } else { 0.6 },
if self.image_zoomed { 0.6 } else { 0.0 },
1.0,
0.0,
time - time_offset,
0.5,
),
),
);
}
} 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();
if t > 0.0 {
hi_image.clone()
} else {
image.clone()
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;
}
.tint(Color32::WHITE.gamma_multiply(zoom_view_opacity))
.paint_at(&ui, image_rect.lerp_towards(&fs_rect, t));
} else {
image
.clone()
.tint(Color32::WHITE.gamma_multiply(zoom_view_opacity))
.paint_at(
ui,
image_rect.lerp_towards(
&fs_rect,
keyframe::ease_with_scaled_time(
functions::EaseInOutCubic,
1.0,
0.0,
time - time_offset,
0.5,
),
),
);
}
}
});
buffers.push(BufferWithTextArea::new(
context_block.buffer.clone(),
text_rect,
zoom_view_opacity,
Color::rgb(255, 255, 255),
ui.ctx(),
));
}
});
}
if self.zoomed {
if ui.input(|i| i.pointer.any_click()) && !link_clicked {