Skip to content

Instantly share code, notes, and snippets.

@airstrike
Created December 14, 2024 19:45
Show Gist options
  • Save airstrike/1e9d5067b58937c6e5c2e5de2b842304 to your computer and use it in GitHub Desktop.
Save airstrike/1e9d5067b58937c6e5c2e5de2b842304 to your computer and use it in GitHub Desktop.
owned LLVM Context pattern for `iced`
use std::collections::HashMap;
use components::sidebar;
use desmos_compiler::{
expressions::{ExpressionId, Expressions},
lang::backends::llvm::{codegen::compile_all_exprs, CompiledExprs},
};
use graph::GraphRenderer;
use iced::{
alignment::Horizontal,
overlay,
widget::{
self,
canvas::Cache,
container, mouse_area, opaque,
pane_grid::{self, Axis, Content, Pane, ResizeEvent},
row,
text_input::{self, focus, Id},
Canvas, Stack, TextInput,
},
Application, Color, Length, Padding, Settings, Task, Vector,
};
mod components;
mod graph;
static DCG_FONT: &[u8; 45324] = include_bytes!("./dcg-icons-2024-08-02.ttf");
struct OwnedCompiledExprs {
context: inkwell::context::Context,
compiled_eqs: CompiledExprs<'static>,
}
impl OwnedCompiledExprs {
fn new() -> Self {
let context = inkwell::context::Context::create();
Self {
context,
compiled_eqs: CompiledExprs::new(),
}
}
fn recompile(&mut self, exprs: &Expressions) {
// SAFETY: Safe because compiled_eqs cannot outlive context
// since they're in the same struct and context is dropped last
unsafe {
self.compiled_eqs = std::mem::transmute(compile_all_exprs(&self.context, exprs));
}
}
fn get(&self) -> &CompiledExprs<'static> {
&self.compiled_eqs
}
}
#[derive(Debug, Clone)]
pub enum Message {
Moved(Vector),
Scaled(f32, Option<Vector>),
EquationChanged(ExpressionId, String),
EquationAdded(String),
ShowError(Option<ExpressionId>),
FocusExpr(usize),
Resized(pane_grid::ResizeEvent),
}
enum PaneType {
Graph,
Sidebar,
}
struct Somsed {
owned_exprs: OwnedCompiledExprs,
panes: pane_grid::State<PaneType>,
graph_caches: HashMap<ExpressionId, Cache>,
parsed_expressions: Expressions,
expressions: HashMap<ExpressionId, String>,
errors: HashMap<ExpressionId, String>,
shown_error: Option<ExpressionId>,
scale: f32,
mid: Vector,
resolution: u32,
}
impl Somsed {
fn new() -> Self {
let graph_caches = HashMap::new();
let expressions = Expressions::new();
let (mut panes, pane) = pane_grid::State::new(PaneType::Sidebar);
panes.split(Axis::Vertical, pane, PaneType::Graph);
Self {
owned_exprs: OwnedCompiledExprs::new(),
panes,
scale: 100.0,
mid: Vector { x: 0.0, y: 0.0 },
resolution: 1000,
errors: HashMap::new(),
parsed_expressions: expressions,
expressions: HashMap::new(),
graph_caches,
shown_error: None,
}
}
fn view(&self) -> pane_grid::PaneGrid<'_, Message> {
pane_grid::PaneGrid::new(&self.panes, move |_, id, _| match id {
PaneType::Graph => Content::new(
Canvas::new(GraphRenderer::new(
self.owned_exprs.get(),
&self.graph_caches,
self.scale,
self.mid,
self.resolution,
))
.width(Length::Fill)
.height(Length::Fill),
),
PaneType::Sidebar => pane_grid::Content::new(sidebar::view(
&self.expressions,
&self.errors,
&self.shown_error,
)),
})
.on_resize(10, Message::Resized)
.width(Length::Fill)
.height(Length::Fill)
}
pub fn clear_caches(&mut self) {
for (_, v) in &mut self.graph_caches {
v.clear();
}
}
fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::Moved(p) => {
self.mid = self.mid + p;
self.clear_caches();
}
Message::EquationChanged(i, s) => {
self.parsed_expressions.set_expr(i, &s);
self.owned_exprs.recompile(&self.parsed_expressions);
self.graph_caches[&i].clear();
}
Message::EquationAdded(s) => {
if let Some(expr_id) = self.parsed_expressions.max_id.checked_sub(1) {
self.parsed_expressions.add_expr(&s);
self.graph_caches
.insert(ExpressionId(expr_id), Cache::new());
self.owned_exprs.recompile(&self.parsed_expressions);
return focus(Id::new(format!("equation_{}", expr_id)));
}
}
Message::Scaled(scale, mid) => {
self.scale = scale;
if let Some(mid) = mid {
self.mid = mid;
}
self.clear_caches();
}
Message::ShowError(i) => {
self.shown_error = i;
}
Message::FocusExpr(i) => return focus(Id::new(format!("equation_{}", i))),
Message::Resized(ResizeEvent { split, ratio }) => {
self.panes.resize(split, ratio);
}
};
Task::none()
}
}
fn main() -> iced::Result {
iced::application("Somsed", Somsed::update, Somsed::view)
.font(DCG_FONT)
.antialiasing(true)
.run_with(|| (Somsed::new(), Task::none()))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment