diff --git a/index.html b/index.html
index 665c1ce..6178d73 100644
--- a/index.html
+++ b/index.html
@@ -8,6 +8,9 @@
+
+
diff --git a/src/lib.rs b/src/lib.rs
index bd84712..3c35a73 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,14 +3,17 @@
// but some rules are too "annoying" or are not applicable for your case.)
#![allow(clippy::wildcard_imports)]
-use std::time::Duration;
+use std::{
+ any::{Any, TypeId},
+ time::Duration,
+};
use chrono::{Local, NaiveDateTime, TimeZone};
use log::{info, Level};
use seed::{prelude::*, *};
use thiserror::Error;
use wasm_timer::{SystemTime, UNIX_EPOCH};
-use web_sys::{ContextAttributes2d, HtmlCanvasElement, HtmlImageElement};
+use web_sys::{ContextAttributes2d, Event, HtmlCanvasElement, HtmlImageElement};
const ROOT_URL: &'static str = "/www/tracer";
@@ -38,6 +41,8 @@ fn init(_: Url, orders: &mut impl Orders) -> Model {
image_ref: ElRef::::default(),
zoom: 1.0,
offset: [0., 0.],
+ current_image_idx: None,
+ mouse_position: None,
}
}
@@ -54,6 +59,8 @@ struct Model {
image_ref: ElRef,
zoom: f64,
offset: [f64; 2],
+ current_image_idx: Option,
+ mouse_position: Option<(i32, i32)>,
}
// ------ ------
@@ -67,11 +74,11 @@ enum Msg {
OnError(AppError),
Rendered,
Received(Data),
- Reset,
SetZoom(f64, [i32; 2]),
Zoom(f64, [i32; 2]),
Move([f64; 2]),
UrlChanged(subs::UrlChanged),
+ SetMousePosition(i32, i32),
}
// `update` describes how to handle each `Msg`.
@@ -127,10 +134,6 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) {
// TODO(wathiede): can this be done less often?
orders.after_next_render(|_| Msg::Rendered).skip();
}
- Msg::Reset => {
- model.zoom = 1.;
- model.offset = [0., 0.];
- }
Msg::SetZoom(scale, offset) => {
if scale < 0.1 {
model.zoom = 0.1;
@@ -157,6 +160,7 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) {
Msg::UrlChanged(subs::UrlChanged(url)) => {
info!("url changed {}", url);
}
+ Msg::SetMousePosition(x, y) => model.mouse_position = Some((x, y)),
}
}
use serde::{Deserialize, Serialize};
@@ -199,17 +203,30 @@ pub enum ImageType {
// ------ ------
// View
// ------ ------
-fn view_metadata_table(data: &Data) -> Node {
- fn row(name: &str, value: T) -> Node
- where
- T: std::fmt::Debug,
- {
- let mut v = format!("{:?}", value);
- if &v == "None" {
- v = "❌".to_string();
- }
- tr![td![name], td![v]]
+fn row(name: &str, value: T) -> Node
+where
+ T: Any + std::fmt::Debug,
+{
+ let value_any = &value as &dyn Any;
+ let t_string = TypeId::of::();
+ let t_f32 = TypeId::of::();
+ let t_f64 = TypeId::of::();
+ let t_t = TypeId::of::();
+ let mut v = if t_t == t_string {
+ format!("{}", value_any.downcast_ref::().unwrap())
+ } else if t_t == t_f32 {
+ format!("{:.2}", value_any.downcast_ref::().unwrap())
+ } else if t_t == t_f64 {
+ format!("{:.2}", value_any.downcast_ref::().unwrap())
+ } else {
+ format!("{value:?}")
+ };
+ if &v == "None" {
+ v = "❌".to_string();
}
+ tr![td![name], td![C!["has-text-right"], v]]
+}
+fn view_metadata_table(data: &Data) -> Vec> {
//❌
//✔️
let d = SystemTime::now()
@@ -223,26 +240,32 @@ fn view_metadata_table(data: &Data) -> Node {
let dt = NaiveDateTime::from_timestamp_opt(data.timestamp, 0).unwrap();
let dt = Local.from_local_datetime(&dt).unwrap();
let rendered_at = dt.format("%Y-%m-%d %H:%M:%S").to_string();
- div![
- div!["Rendered @ ", &rendered_at],
- div![
- C![IF!(fresh => "fresh")],
- humantime::format_duration(d).to_string(),
- " ago"
+ /*
+ a![
+ span![C!["icon"], i![C!["fa-solid", "fa-up-right-from-square"]]],
+ ev(
+ Ev::Click,
+ make_external_link(format!("{ROOT_URL}/{}?t={}", im.image, data.timestamp))
+ )
+ ],
+ */
+ nodes![
+ tr![th![attrs! {At::ColSpan=>2}, "Render settings"]],
+ tr![
+ td![rendered_at,],
+ td![
+ C![IF!(fresh => "fresh")],
+ humantime::format_duration(d).to_string(),
+ " ago"
+ ]
],
- table![
- C!["table", "is-striped", "is-narrow"],
- tbody![
- tr![th!["Name"], th!["Value"]],
- row("render_time_seconds", data.render_time_seconds),
- row("subsamples", data.scene.subsamples),
- row("adaptive_subsampling", data.scene.adaptive_subsampling),
- row("num_threads", data.scene.num_threads),
- row("width", data.scene.width),
- row("height", data.scene.height),
- row("global_illumination", data.scene.global_illumination),
- ],
- ]
+ row("render_time_seconds", data.render_time_seconds),
+ row("subsamples", data.scene.subsamples),
+ row("adaptive_subsampling", data.scene.adaptive_subsampling),
+ row("num_threads", data.scene.num_threads),
+ row("width", data.scene.width),
+ row("height", data.scene.height),
+ row("global_illumination", data.scene.global_illumination),
]
}
@@ -293,8 +316,10 @@ fn draw(
&image, sx, sy, sw, sh, dx, dy, dw, dh,
)
.unwrap();
+}
- //let color = ctx.get_image_data(10.0, 10.0, 11.0, 11.0).unwrap().data();
+fn make_external_link(href: String) -> impl FnOnce(Event) + Clone {
+ |_| Url::go_and_load_with_str(href)
}
fn view_image(
im: &ImageMetadata,
@@ -303,15 +328,13 @@ fn view_image(
timestamp: i64,
) -> Node {
let url = format!("{ROOT_URL}/{}?t={}", im.image, timestamp);
- let href = url.clone();
- let go = |_| Url::go_and_load_with_str(href);
let attrs = if let Some(canvas) = image_canvas.get() {
// Width based on browser layout of canvas and it's parent (which would be set to
// 'width:100%' in CSS.
let width = canvas.client_width();
// But we need to compute an aspect correct height, because the browser can't figure this
// out for us (because I don't know CSS that well).
- let height = width as f32 * im.ratio;
+ let height = width as f32 / im.ratio;
Some(attrs![
At::Width => px(width),
At::Height => px(height),
@@ -332,7 +355,7 @@ fn view_image(
if e.buttons() != 0 {
Msg::Move([e.movement_x() as f64, e.movement_y() as f64])
} else {
- Msg::Move([0., 0.])
+ Msg::SetMousePosition(e.offset_x(), e.offset_y())
}
}),
wheel_ev(Ev::Wheel, |event| {
@@ -345,7 +368,6 @@ fn view_image(
})
}),
],
- button![C!["button"], "View image", ev(Ev::Click, go)],
img![
el_ref(image_ref),
attrs! {
@@ -363,9 +385,9 @@ fn view_debug_buttons(
canvas_size: (i32, i32),
zoom: f64,
offset: [f64; 2],
-) -> Node {
- let zoom_in = 0.1;
- let zoom_out = -0.1;
+ image_canvas: &ElRef,
+ mouse_position: Option<(i32, i32)>,
+) -> Vec> {
let (image_width, image_height) = image_size;
let (canvas_width, canvas_height) = canvas_size;
let one2one = image_width as f64 / canvas_width as f64;
@@ -373,85 +395,30 @@ fn view_debug_buttons(
(canvas_width - image_width as i32) / 2,
(canvas_height - image_height as i32) / 2,
];
- div![
- p![format!(
- "Z: {:.4} Off: {:.2}x{:.2}",
- zoom, offset[0], offset[1]
- )],
- p![format!("Mid {}x{}", canvas_width / 2, canvas_height / 2)],
- p![format!("Max {}x{}", canvas_width, canvas_height)],
- div![
- C!["buttons", "has-addons"],
- button![
- C!["button", "is-danger", "is-fullwidth"],
- ev(Ev::Click, |_| Msg::Reset),
- "Reset",
- ],
- ],
- div![
- C!["buttons", "has-addons", "is-centered"],
- button![
+ let (r, g, b) = get_hover_color(image_canvas, mouse_position);
+ nodes![
+ tr![th![attrs! {At::ColSpan=>2}, "View settings"]],
+ row("Zoom", format!("{:.2}", zoom)),
+ row("X Offset", format!("{:.2}", offset[0])),
+ row("Y Offset", format!("{:.2}", offset[1])),
+ tr![td!["Hover"],td![
+ style! {
+ St::BackgroundColor => format!("rgb({},{},{})",r,g,b),
+ },
+ ]]
+ tr![th![attrs! {At::ColSpan=>2}, "Set Zoom"]],
+ tr![
+ td![button![
C!["button"],
ev(Ev::Click, move |_| Msg::SetZoom(one2one, one2one_offset)),
"1:1",
- ],
- button![
+ ],],
+ td![button![
C!["button"],
ev(Ev::Click, move |_| Msg::SetZoom(1., [0, 0])),
"Fit",
- ],
- ],
- div![
- C!["buttons", "has-addons", "is-centered"],
- button![
- C!["button"],
- ev(Ev::Click, move |_| Msg::Zoom(zoom_in, [0, 0])),
- "+ @ 0x0",
- ],
- button![
- C!["button"],
- ev(Ev::Click, move |_| Msg::Zoom(zoom_out, [0, 0])),
- "- @ 0x0",
- ],
- ],
- div![
- C!["buttons", "has-addons", "is-centered"],
- button![
- C!["button"],
- ev(Ev::Click, move |_| Msg::Zoom(
- zoom_in,
- [canvas_width / 2, canvas_height / 2]
- )),
- "+ @ mid",
- ],
- button![
- C!["button"],
- ev(Ev::Click, move |_| Msg::Zoom(
- zoom_out,
- [canvas_width / 2, canvas_height / 2]
- )),
- "- @ mid",
- ],
- ],
- div![
- C!["buttons", "has-addons", "is-centered"],
- button![
- C!["button"],
- ev(Ev::Click, move |_| Msg::Zoom(
- zoom_in,
- [canvas_width, canvas_height]
- )),
- "+ @ max",
- ],
- button![
- C!["button"],
- ev(Ev::Click, move |_| Msg::Zoom(
- zoom_out,
- [canvas_width, canvas_height]
- )),
- "- @ max",
- ],
- ],
+ ]]
+ ]
]
}
@@ -461,30 +428,59 @@ fn view_data(
image_ref: &ElRef,
zoom: f64,
offset: [f64; 2],
+ mouse_position: Option<(i32, i32)>,
) -> Node {
- let mut canvas_width = 128;
- let mut canvas_height = 128;
+ let mut canvas_width = 1024;
+ let mut canvas_height = 1024;
if let Some(c) = image_canvas.get() {
canvas_width = c.width() as i32;
canvas_height = c.height() as i32;
}
let im = data.image_metadata.iter().nth(0);
- let image_size = im.map(|im| im.size).unwrap_or((512, 512));
+ let image_size = im.map(|im| im.size).unwrap_or((1024, 1024));
div![
C!["columns"],
div![
C!["column"],
div![
C!["tabs"],
- ul![data.image_metadata.iter().map(|im| li![a![&im.name]])]
+ ul![data
+ .image_metadata
+ .iter()
+ .map(|im| li![a![span![&im.name],]])]
],
- im.map(|im| view_image(im, image_canvas, image_ref, data.timestamp))
+ im.map(|im| view_image(im, image_canvas, image_ref, data.timestamp)),
],
div![
C!["column", "is-narrow"],
- view_metadata_table(data),
- view_debug_buttons(image_size, (canvas_width, canvas_height), zoom, offset),
- ],
+ table![
+ C!["table", "is-striped", "is-size-7"],
+ tbody![
+ view_metadata_table(data),
+ view_debug_buttons(
+ image_size,
+ (canvas_width, canvas_height),
+ zoom,
+ offset,
+ image_canvas,
+ mouse_position
+ ),
+ ]
+ ],
+ /*
+ im.map(|im| {
+ let url = format!("{ROOT_URL}/{}?t={}", im.image, data.timestamp);
+ div![
+ C!["buttons", "has-addons", "is-centered"],
+ button![
+ C!["button"],
+ "View image",
+ ev(Ev::Click, make_external_link(url.clone()))
+ ]
+ ]
+ }),
+ */
+ ]
]
}
@@ -502,9 +498,13 @@ fn view(model: &Model) -> Node {
&model.image_canvas,
&model.image_ref,
model.zoom,
- model.offset
+ model.offset,
+ model.mouse_position,
),
- None => h1!["Loading...."],
+ None => section![
+ C!["content", "hero", "is-large", "is-fullheight"],
+ div![C!["hero-body"], p![C!["title"], "Loading...."]]
+ ],
},
error,
div![
@@ -517,6 +517,49 @@ fn view(model: &Model) -> Node {
]
}
+fn get_hover_color(
+ image_canvas: &ElRef,
+ mouse_position: Option<(i32, i32)>,
+) -> (u8, u8, u8) {
+ let mouse_position = if let Some(mouse_position) = mouse_position {
+ mouse_position
+ } else {
+ return (0, 0, 0);
+ };
+ let canvas = if let Some(canvas) = image_canvas.get() {
+ canvas
+ } else {
+ return (0, 0, 0);
+ };
+ let mut ca2 = ContextAttributes2d::new();
+ // Chrome warning:
+ // Canvas2D: Multiple readback operations using getImageData are faster with the
+ // willReadFrequently attribute set to true. See:
+ // https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-will-read-frequently
+ ca2.will_read_frequently(true);
+ let ctx = canvas
+ .get_context_with_context_options("2d", &ca2)
+ .expect("Problem getting canvas context")
+ .expect("The canvas context is empty")
+ .dyn_into::()
+ .expect("Problem casting as web_sys::CanvasRenderingContext2d");
+
+ let x = mouse_position.0 as f64;
+ let y = mouse_position.1 as f64;
+ let c = ctx.get_image_data(x, y, x + 1., y + 1.).unwrap().data();
+ (c[0], c[1], c[2])
+ /*
+ let c_str = format!("{},{},{}", c[0], c[1], c[2]);
+ button![
+ C!["button", "is-fullwidth"],
+ style! {
+ St::BackgroundColor => format!("rgb({c_str})"),
+ },
+ c_str
+ ]
+ */
+}
+
// ------ ------
// Start
// ------ ------