Skip to content

Commit 91bf416

Browse files
committed
t
1 parent 5b3d4df commit 91bf416

File tree

2 files changed

+162
-20
lines changed

2 files changed

+162
-20
lines changed

src/shared/image_viewer_modal.rs

Lines changed: 161 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,28 @@ live_design! {
1313
use crate::shared::styles::*;
1414
use crate::shared::icon_button::RobrixIconButton;
1515
IMG_DEFAULT = dep("crate://self/resources/img/default_image.png")
16+
17+
ZoomableImage = {{ZoomableImage}} {
18+
width: Fill,
19+
height: Fill,
20+
flow: Overlay,
21+
22+
// Transform state
23+
scale: 1.0,
24+
offset: (0.0, 0.0),
25+
26+
// Interaction state
27+
dragging: false,
28+
last_mouse_pos: (0.0, 0.0),
29+
30+
// Internal image widget to hold the actual texture
31+
inner_image = <Image> {
32+
width: Fill,
33+
height: Fill,
34+
fit: Smallest,
35+
source: (IMG_DEFAULT)
36+
}
37+
}
1638
pub ImageViewerModal = {{ImageViewerModal}} {
1739
width: Fill, height: Fill
1840
image_modal = <Modal> {
@@ -31,13 +53,11 @@ live_design! {
3153
image_container = <View> {
3254
width: Fill,
3355
height: Fill,
34-
flow: Down,
35-
align: {x: 0.5}
36-
image = <Image> {
37-
height: Fill
38-
width: Fill
39-
fit: Smallest,
40-
source: (IMG_DEFAULT)
56+
flow: Overlay,
57+
align: {x: 0.5, y: 0.5}
58+
zoomable_image = <ZoomableImage> {
59+
width: Fill,
60+
height: Fill,
4161
}
4262
}
4363
<View> {
@@ -130,6 +150,120 @@ pub enum ImageViewerModalAction {
130150
None,
131151
}
132152

153+
#[derive(Live, LiveHook, Widget)]
154+
pub struct ZoomableImage {
155+
#[deref]
156+
view: View,
157+
158+
// Transform state
159+
#[live]
160+
scale: f64,
161+
#[live]
162+
offset: DVec2,
163+
164+
// Interaction state
165+
#[live]
166+
dragging: bool,
167+
#[live]
168+
last_mouse_pos: DVec2,
169+
}
170+
171+
impl Widget for ZoomableImage {
172+
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
173+
self.view.handle_event(cx, event, scope);
174+
175+
match event {
176+
Event::MouseDown(e) => {
177+
if e.button.is_primary() {
178+
self.dragging = true;
179+
self.last_mouse_pos = e.abs;
180+
cx.set_key_focus(self.view.area());
181+
self.redraw(cx);
182+
}
183+
}
184+
Event::MouseUp(e) => {
185+
if e.button.is_primary() {
186+
self.dragging = false;
187+
self.redraw(cx);
188+
}
189+
}
190+
Event::MouseMove(e) => {
191+
if self.dragging {
192+
let delta = e.abs - self.last_mouse_pos;
193+
194+
// Scale delta by inverse of zoom to maintain consistent pan speed
195+
let scaled_delta = delta / self.scale;
196+
self.offset += scaled_delta;
197+
198+
self.last_mouse_pos = e.abs;
199+
self.redraw(cx);
200+
}
201+
}
202+
Event::Scroll(e) => {
203+
// Zoom in/out with mouse wheel
204+
let zoom_factor = if e.scroll.y > 0.0 { 1.1 } else { 0.9 };
205+
let old_scale = self.scale;
206+
self.scale *= zoom_factor;
207+
self.scale = self.scale.clamp(0.1, 10.0); // Clamp zoom level
208+
209+
if self.scale != old_scale {
210+
// Get widget's rect
211+
let widget_rect = self.view.area().rect(cx);
212+
let widget_center = DVec2{
213+
x: widget_rect.pos.x as f64 + widget_rect.size.x as f64 * 0.5,
214+
y: widget_rect.pos.y as f64 + widget_rect.size.y as f64 * 0.5,
215+
};
216+
217+
// Calculate mouse position relative to widget center
218+
let mouse_rel = e.abs - widget_center;
219+
220+
// Adjust offset to zoom toward mouse position
221+
let scale_ratio = self.scale / old_scale;
222+
self.offset = self.offset * scale_ratio + mouse_rel * (1.0 - scale_ratio) / self.scale;
223+
224+
self.redraw(cx);
225+
}
226+
}
227+
_ => {}
228+
}
229+
}
230+
231+
fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
232+
// Apply transform by modifying the drawing context
233+
// This is a simplified approach - in a full implementation, we'd use custom shaders
234+
if self.scale != 1.0 || self.offset.x != 0.0 || self.offset.y != 0.0 {
235+
// Update the inner image's transform properties if possible
236+
// For now, just redraw - the actual transform will be visual feedback
237+
self.view.redraw(cx);
238+
}
239+
self.view.draw_walk(cx, scope, walk)
240+
}
241+
}
242+
243+
impl ZoomableImage {
244+
pub fn reset_transform(&mut self, cx: &mut Cx) {
245+
self.scale = 1.0;
246+
self.offset = DVec2 {x: 0.0, y: 0.0};
247+
self.dragging = false;
248+
self.redraw(cx);
249+
}
250+
251+
pub fn set_image_texture(&mut self, cx: &mut Cx, data: &[u8]) -> Result<(), String> {
252+
// Load the image data into the internal image widget
253+
let image = self.view.image(id!(inner_image));
254+
match crate::utils::load_png_or_jpg(&image, cx, data) {
255+
Ok(()) => {
256+
// Reset transform when setting new image
257+
self.reset_transform(cx);
258+
Ok(())
259+
}
260+
Err(e) => {
261+
Err(format!("Failed to load image: {:?}", e))
262+
}
263+
}
264+
}
265+
}
266+
133267
impl Widget for ImageViewerModal {
134268
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
135269
self.view.handle_event(cx, event, scope);
@@ -187,18 +321,19 @@ impl ImageViewerModal {
187321
}
188322
}
189323

190-
/// Load image data into the Image widget
324+
/// Load image data into the ZoomableImage widget
191325
fn load_image_data(&mut self, cx: &mut Cx, data: &[u8]) {
192-
let image = self.view.image(id!(image));
193-
match load_png_or_jpg(&image, cx, data) {
194-
Ok(()) => {
195-
self.view.view(id!(loading_view)).set_visible(cx, false);
196-
self.view.view(id!(error_label_view)).set_visible(cx, false);
197-
self.view.image(id!(image)).set_visible(cx, true);
198-
}
199-
Err(e) => {
200-
error!("Failed to load image: {:?}", e);
201-
self.show_error_state(cx);
326+
println!("load_image_data");
327+
if let Some(mut zoomable_image) = self.view.zoomable_image(id!(zoomable_image)).borrow_mut() {
328+
match zoomable_image.set_image_texture(cx, data) {
329+
Ok(()) => {
330+
self.view.view(id!(loading_view)).set_visible(cx, false);
331+
self.view.view(id!(error_label_view)).set_visible(cx, false);
332+
self.view.view(id!(image_container)).set_visible(cx, true);
333+
}
334+
Err(e) => {
335+
self.show_error_state(cx);
336+
}
202337
}
203338
}
204339
}
@@ -207,18 +342,24 @@ impl ImageViewerModal {
207342
fn show_loading_state(&mut self, cx: &mut Cx) {
208343
self.view.view(id!(loading_view)).set_visible(cx, true);
209344
self.view.view(id!(error_label_view)).set_visible(cx, false);
210-
self.view.image(id!(image)).set_visible(cx, false);
345+
self.view.view(id!(image_container)).set_visible(cx, false);
211346
}
212347

213348
/// Show error state
214349
fn show_error_state(&mut self, cx: &mut Cx) {
215350
self.view.view(id!(loading_view)).set_visible(cx, false);
216351
self.view.view(id!(error_label_view)).set_visible(cx, true);
217-
self.view.image(id!(image)).set_visible(cx, false);
352+
self.view.view(id!(image_container)).set_visible(cx, false);
218353
}
219354
fn close(&mut self, cx: &mut Cx) {
220355
self.mxc_uri = None;
221356
self.image_loaded = false;
357+
358+
// Reset the zoomable image transform
359+
if let Some(mut zoomable_image) = self.view.zoomable_image(id!(zoomable_image)).borrow_mut() {
360+
zoomable_image.reset_transform(cx);
361+
}
362+
222363
self.modal(id!(image_modal)).close(cx);
223364
}
224365
}

src/shared/text_or_image.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ impl Widget for TextOrImage {
7878
// so we do nothing this condition.
7979
let image_viewer_modal = get_global_image_viewer_modal(cx);
8080
image_viewer_modal.open(cx, Some(mxc_uri.clone()));
81+
println!("Image clicked");
8182
}
8283
_ => { },
8384
}

0 commit comments

Comments
 (0)