Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
340 changes: 43 additions & 297 deletions Cargo.lock

Large diffs are not rendered by default.

25 changes: 23 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "2"
exclude = ["crates/loki-file-access", "crates/loki-opc", "crates/appthere-canvas"]
members = [
"crates/appthere-ui",
"crates/iris-app",
"crates/iris-canvas",
"crates/iris-pixel",
Expand Down Expand Up @@ -31,6 +32,9 @@ appthere-color = { version = "0.1" }
appthere-file-access = { package = "loki-file-access", path = "crates/loki-file-access" }
loki-opc = { path = "crates/loki-opc", features = ["serde"] }

# AppThere UI shared crate
appthere-ui = { path = "crates/appthere-ui" }

# Iris internal crates (path deps within this workspace)
iris-ops = { path = "crates/iris-ops" }
iris-pixel = { path = "crates/iris-pixel" }
Expand All @@ -40,8 +44,8 @@ iris-canvas = { path = "crates/iris-canvas" }
iris-plugin-api = { path = "crates/iris-plugin-api" }

# External dependencies
dioxus = { version = "0.7", features = ["native"] }
vello = { version = "0.4" }
dioxus = { version = "=0.7.4", features = ["native"] }
vello = { version = "0.6" }
wgpu = { version = "26" }
kurbo = { version = "0.11" }
parley = { version = "0.2" }
Expand All @@ -61,6 +65,23 @@ proptest = { version = "1" }
bytemuck = { version = "1", features = ["derive"] }
png = { version = "0.17" }

[patch.crates-io]
# PATCH: fixes missing fontconfig_sys alias and dlopen/static feature-unification
# conflict with blitz-dom's fontique 0.6 — remove when fontique >0.8.0 on
# crates.io restores the alias and resolves the linkage conflict.
fontique = { path = "crates/patches/fontique" }
# PATCH: vendors dioxus-native-dom to avoid runtime unimplemented!() panics in
# HtmlEventConverter (composition, touch, scroll, etc.) — remove when upstream
# implements the converters Loki requires.
dioxus-native-dom = { path = "crates/patches/dioxus-native-dom" }
# PATCH: handle_click fix for wgpu canvas focus behaviour.
# Remove when upstream blitz-dom makes tabindex focus on click work for
# non-input elements.
blitz-dom = { path = "crates/patches/blitz-dom" }
# PATCH: forwards WindowEvent::Touch to Dioxus touch handlers.
# Remove when blitz-shell implements touch natively.
blitz-shell = { path = "crates/patches/blitz-shell" }

[profile.dev]
opt-level = 1 # faster compiles, still debuggable

Expand Down
2 changes: 1 addition & 1 deletion crates/appthere-canvas/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ font-cache = ["dep:peniko"]
thiserror = "2"
tracing = "0.1"
wgpu = { version = "26", optional = true }
dioxus = { version = "0.7", features = ["native"], optional = true }
dioxus = { version = "0.7.4", features = ["native"], optional = true }
tokio = { version = "1", features = ["time", "sync"], optional = true }
peniko = { version = "0.5", optional = true }
1 change: 0 additions & 1 deletion crates/appthere-ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ path = "src/lib.rs"

[dependencies]
dioxus = { workspace = true }
thiserror = { workspace = true }
10 changes: 8 additions & 2 deletions crates/appthere-ui/src/components/home_tab/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub struct RecentDocument {
/// All interactive elements (template cards, document rows, buttons) meet this.
#[component]
pub fn AtHomeTab(props: AtHomeTabProps) -> Element {
let viewport_width = use_signal(|| 375.0_f32);
let viewport_width = use_signal(|| props.viewport_width_px);
let is_desktop = viewport_width() >= BREAKPOINT_DESKTOP_PX;

// Holds the last file-picker error message, if any.
Expand Down Expand Up @@ -201,7 +201,7 @@ pub fn AtHomeTab(props: AtHomeTabProps) -> Element {
fg = COLOR_STATUS_ERROR_TEXT,
size = FONT_SIZE_LABEL,
),
"Could not open file picker: {err}"
"{props.pick_error_prefix}: {err}"
}
}

Expand Down Expand Up @@ -262,6 +262,12 @@ pub struct AtHomeTabProps {
/// Message shown when `recent_documents` is empty.
pub empty_recent_label: String,

// COMPAT(loki): loki-text's AtHomeTab call sites need updating to pass pick_error_prefix and viewport_width_px props
/// Prefix for the file-picker error banner.
pub pick_error_prefix: String,
/// Viewport width in CSS pixels, used for responsive layout detection.
pub viewport_width_px: f32,

// ── Callbacks ─────────────────────────────────────────────────────────────
/// Called when a template card is selected. Argument is the index into `templates`.
pub on_template_select: EventHandler<usize>,
Expand Down
16 changes: 7 additions & 9 deletions crates/iris-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ name = "iris"
path = "src/main.rs"

[dependencies]
iris-canvas = { workspace = true }
iris-pixel = { workspace = true }
iris-vector = { workspace = true }
iris-ops = { workspace = true }
iris-aif = { workspace = true }
iris-plugin-api = { workspace = true }
dioxus = { workspace = true }
tracing = { workspace = true }
uuid = { workspace = true }
appthere-ui = { workspace = true }
iris-canvas = { workspace = true }
iris-pixel = { workspace = true }
dioxus = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uuid = { workspace = true }
20 changes: 20 additions & 0 deletions crates/iris-app/src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2024 AppThere Project
// SPDX-License-Identifier: Apache-2.0

use appthere_ui::AtThemeContext;
use dioxus::prelude::*;

use crate::layout::AppLayout;
use crate::state::AppState;

#[component]
pub fn App() -> Element {
provide_context(AtThemeContext::default());
let state = use_signal(AppState::default);
rsx! {
// Blitz's UA stylesheet sets `body { margin: 8px }`. Injecting an author
// stylesheet overrides it — author > user-agent in the CSS cascade.
style { "html, body {{ margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; box-sizing: border-box; }}" }
AppLayout { state }
}
}
70 changes: 70 additions & 0 deletions crates/iris-app/src/canvas_area.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2024 AppThere Project
// SPDX-License-Identifier: Apache-2.0

use appthere_ui::tokens::colors::{COLOR_SURFACE_BASE, COLOR_TEXT_ON_CHROME_SECONDARY};
use appthere_ui::tokens::typography::FONT_SIZE_BODY;
use dioxus::prelude::*;
use iris_canvas::IrisCanvas;
use iris_pixel::LayerTree;

use crate::state::AppState;

// TODO(iris): SPEC.md §11.3 — measure actual CanvasArea dimensions via onmounted
const CANVAS_WIDTH: u32 = 800;
const CANVAS_HEIGHT: u32 = 600;

#[component]
pub fn CanvasArea(mut state: Signal<AppState>) -> Element {
// All hooks called unconditionally before any early return (Dioxus rules).
let tree_signal = use_signal(move || {
state
.read()
.document
.as_ref()
.map(|d| d.tree.clone())
.unwrap_or_else(|| LayerTree::new(1, 1, 96.0, 96.0))
});

let viewport_signal = use_signal(move || {
state
.read()
.document
.as_ref()
.map(|d| d.viewport.clone())
.unwrap_or_default()
});

use_effect(move || {
let vp = viewport_signal.read().clone();
if let Some(doc) = state.write().document.as_mut() {
doc.viewport = vp;
}
});

let doc_exists = state.read().document.is_some();

// Early return AFTER all hooks.
if !doc_exists {
return rsx! {
div {
style: "flex: 1; display: flex; align-items: center; \
justify-content: center; background-color: {COLOR_SURFACE_BASE}; \
color: {COLOR_TEXT_ON_CHROME_SECONDARY}; \
font-size: {FONT_SIZE_BODY}px;",
"No document open"
}
};
}

rsx! {
div {
style: "flex: 1; overflow: hidden; background-color: {COLOR_SURFACE_BASE};",
IrisCanvas {
tree: tree_signal,
viewport: viewport_signal,
width: CANVAS_WIDTH,
height: CANVAS_HEIGHT,
}
}
}
}
59 changes: 59 additions & 0 deletions crates/iris-app/src/home_tab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2024 AppThere Project
// SPDX-License-Identifier: Apache-2.0

use appthere_ui::{AtHomeTab, BuiltinTemplate};
use dioxus::prelude::*;

use crate::state::{AppState, OpenDocument};

#[component]
pub fn IrisHomeTab(mut state: Signal<AppState>) -> Element {
let templates = vec![
BuiltinTemplate {
name: "Blank Canvas".to_string(),
description: String::new(),
format_label: "AIF".to_string(),
},
BuiltinTemplate {
name: "A4 Photo".to_string(),
description: String::new(),
format_label: "AIF".to_string(),
},
BuiltinTemplate {
name: "Social Media".to_string(),
description: String::new(),
format_label: "AIF".to_string(),
},
];

// TODO(iris): SPEC.md §11 — measure actual window width for responsive layout
let viewport_width_px = 1280.0_f32;

rsx! {
AtHomeTab {
app_name: "Iris".to_string(),
templates,
recent_documents: vec![],
templates_label: "Start from a template".to_string(),
recent_label: "Recent documents".to_string(),
browse_label: "Browse templates".to_string(),
open_file_label: "Open file".to_string(),
empty_recent_label: "No recent files".to_string(),
pick_error_prefix: "Could not open file".to_string(),
viewport_width_px,
on_template_select: move |_idx| {
let doc = OpenDocument::new_blank(800, 600, "Untitled");
state.write().document = Some(doc);
state.write().active_tab_index = 1;
},
on_browse_templates: move |_| {},
on_recent_open: move |_| {},
on_open_file: move |_| {
// TODO(iris): Phase 3 — wire async file picker with appthere_file_access
let doc = OpenDocument::new_blank(800, 600, "Untitled");
state.write().document = Some(doc);
state.write().active_tab_index = 1;
},
}
}
}
Loading
Loading