1use alloc::rc::Rc;
38use alloc::string::String;
39use alloc::vec::Vec;
40
41use gfx::path::Path;
42use gfx::xform::Matrix;
43use gfx::{Surface, FMT_ARGB, FMT_PARGB};
44use img_core::mip::MipChain;
45use img_core::resample::{RS_BILINEAR, RS_NEAREST};
46use img_core::xform::{affine, XF_TRANSPARENT};
47use img_svg::{FontSpec, Opts, Svg, TextHook};
48use text::{Gp, Text, FONT_BOLD, FONT_REG, FONT_SEMI};
49
50use crate::canvas::{CanvasContent, CanvasDesc, CanvasGpu, CanvasView};
51use crate::cmd::{Draw, DrawRef, Gfx};
52use crate::consts::*;
53use crate::i18n::tr;
54use crate::keys::*;
55use crate::Ui;
56
57pub const IV_WHEEL_SCROLL: u32 = 1; pub const IV_NO_GRID: u32 = 2; pub const IV_SMOOTH: u32 = 4; pub const IV_NO_BARS: u32 = 8; pub const IV_NO_CHECKER: u32 = 16; pub const IV_TEXT_COLOR: u32 = 32; const ZOOM_MIN: f64 = 0.01;
66const ZOOM_MAX: f64 = 64.0;
67const STEPS: [f64; 26] = [
69 0.01, 0.02, 0.03, 0.05, 0.08, 0.12, 0.17, 0.25, 0.33, 0.5, 0.67, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0, 12.0, 16.0, 24.0,
70 32.0, 48.0, 64.0,
71];
72const NEAREST_FROM: f64 = 4.0; const GRID_FROM: f64 = 8.0;
74const SVG_CACHE: usize = 64; fn part(id: u32, k: u32) -> u32 {
77 id.wrapping_mul(8).wrapping_add(k) | 0x8000_0000
78}
79
80struct Fnv(u64);
82
83impl Fnv {
84 fn new() -> Fnv {
85 Fnv(0xCBF2_9CE4_8422_2325)
86 }
87 fn u64(&mut self, v: u64) {
88 for b in v.to_le_bytes() {
89 self.0 ^= b as u64;
90 self.0 = self.0.wrapping_mul(0x0000_0100_0000_01B3);
91 }
92 }
93}
94
95#[derive(Clone, Default, Debug)]
99pub struct SvgFonts {
100 pub faces: Vec<(String, u32, bool, u32)>,
102 pub mono: Option<u32>,
104}
105
106pub struct SvgText<'a> {
108 pub text: &'a mut Text,
109 pub fonts: &'a SvgFonts,
110}
111
112impl SvgText<'_> {
113 fn pick(&self, f: &FontSpec) -> (u32, bool) {
115 for fam in f.family.split(',') {
116 let fam = fam.trim().trim_matches(|c| c == '"' || c == '\'').to_lowercase();
117 if fam == "monospace" {
118 if let Some(m) = self.fonts.mono.filter(|&m| self.text.valid(m)) {
119 return (m, f.italic);
120 }
121 }
122 let best = self
123 .fonts
124 .faces
125 .iter()
126 .filter(|x| x.0 == fam && self.text.valid(x.3))
127 .min_by_key(|x| (x.2 != f.italic) as u32 * 10000 + (x.1 as i32 - f.weight as i32).unsigned_abs());
128 if let Some(b) = best {
129 return (b.3, f.italic && !b.2);
130 }
131 }
132 let font = if f.weight >= 600 {
133 FONT_BOLD
134 } else if f.weight >= 500 {
135 FONT_SEMI
136 } else {
137 FONT_REG
138 };
139 (font, f.italic)
140 }
141
142 fn run(&mut self, s: &str, f: &FontSpec, px: f32, mut out: Option<(&mut Path, f32, f32)>) -> f32 {
144 let (font, slant) = self.pick(f);
145 let t = &mut *self.text;
146 let n0 = out.as_mut().map_or(0, |o| o.0.pts_mut().len());
147 let mut pen = 0.0f32;
148 let mut prev: Option<(u32, u32)> = None;
149 for ch in s.chars() {
150 let cp = ch as u32;
151 let mut ff = font;
152 if !t.has(font, cp) {
153 if let Some(alt) = t.fallback_for(cp) {
154 ff = alt;
155 }
156 }
157 let g = t.glyph(ff, cp);
158 let upem = t.upem(ff).max(1) as f32;
159 let sx = px / upem;
160 if let Some((pf, pg)) = prev {
161 if pf == ff {
162 pen += t.kern(ff, pg, g) as f32 * sx;
163 }
164 }
165 if let Some((path, x, y)) = out.as_mut() {
166 t.gp = Gp { x: *x + pen, y: *y, sx, sy: sx, dx: 0, dy: 0 };
167 core::mem::swap(&mut t.path, *path);
168 t.glyph_path(ff, g);
169 core::mem::swap(&mut t.path, *path);
170 }
171 pen += t.adv(ff, g) as f32 * sx;
172 prev = Some((ff, g));
173 }
174 if slant {
176 if let Some((path, _, y)) = out {
177 for p in path.pts_mut()[n0..].iter_mut() {
178 p.0 += (y - p.1) * 0.21;
179 }
180 }
181 }
182 pen
183 }
184}
185
186impl TextHook for SvgText<'_> {
187 fn outline(&mut self, path: &mut Path, s: &str, font: &FontSpec, px: f32, x: f32, y: f32) -> f32 {
188 self.run(s, font, px, Some((path, x, y)))
189 }
190 fn advance(&mut self, s: &str, font: &FontSpec, px: f32) -> f32 {
191 self.run(s, font, px, None)
192 }
193}
194
195#[derive(Default)]
199pub(crate) struct S {
200 svgs: Vec<(u64, Rc<SvgDraw>)>,
201 fonts: Option<Rc<SvgFonts>>,
202}
203
204pub(crate) struct SvgDraw {
206 pub(crate) doc: Rc<Svg>,
207 pub(crate) color: u32,
208 pub(crate) fonts: Rc<SvgFonts>,
209}
210
211impl Draw for SvgDraw {
212 fn draw(&self, g: &mut Gfx, r: [i32; 4]) {
213 let (x, y) = (r[0] + g.dc.ox, r[1] + g.dc.oy);
214 let clip = g.dc.clip;
215 let mut th = SvgText { text: &mut *g.text, fonts: &self.fonts };
216 let mut o = Opts { color: self.color, text: Some(&mut th) };
217 self.doc.render_rect(g.sf, x as f64, y as f64, r[2] as f64, r[3] as f64, Some(clip), &mut o);
218 }
219}
220
221impl Ui {
222 pub fn svg_fonts(&mut self) -> Rc<SvgFonts> {
225 let mut faces: Vec<(String, u32, bool, u32)> = Vec::new();
226 for fam in self.font_registered() {
227 for w in [100, 200, 300, 400, 500, 600, 700, 800, 900] {
228 for it in [false, true] {
229 if let Some(id) = self.font_registered_face(&fam, w, it) {
230 faces.push((fam.to_lowercase(), w, it, id));
231 }
232 }
233 }
234 }
235 let mono = self.m.term.mono;
236 if let Some(f) = &self.m.svg.fonts {
237 if f.faces == faces && f.mono == mono {
238 return f.clone();
239 }
240 }
241 let f = Rc::new(SvgFonts { faces, mono });
242 self.m.svg.fonts = Some(f.clone());
243 f
244 }
245
246 #[cfg_attr(jfgui_dev, track_caller)]
248 pub fn svg(&mut self, id: u32, doc: &Rc<Svg>, w: i32, h: i32) -> u32 {
249 let _src = self.src_scope();
250 let (iw, ih) = doc.size();
251 let (w, h) = match (w > 0, h > 0) {
252 (true, true) => (w as f64, h as f64),
253 (true, false) => (w as f64, w as f64 * ih / iw),
254 (false, true) => (h as f64 * iw / ih, h as f64),
255 (false, false) => (iw, ih),
256 };
257 let pw = self.dp((w + 0.5).clamp(1.0, 1.0e5) as i32);
258 let ph = self.dp((h + 0.5).clamp(1.0, 1.0e5) as i32);
259 let r = self.place(pw, ph);
260 let disabled = self.wp.ctx.disabled_depth != 0;
261 let color = self.color(if disabled { T_TEXT_DISABLED } else { T_TEXT });
262 let fonts = self.svg_fonts();
263 let mut hh = Fnv::new();
264 hh.u64(Rc::as_ptr(doc) as usize as u64);
265 hh.u64(color as u64);
266 hh.u64(Rc::as_ptr(&fonts) as usize as u64);
267 let key = hh.0;
268 let s = &mut self.m.svg;
269 s.svgs.retain(|(_, d)| Rc::strong_count(&d.doc) > 1);
271 let d = match s.svgs.iter().find(|(k, _)| *k == key) {
272 Some((_, d)) => d.clone(),
273 None => {
274 if s.svgs.len() >= SVG_CACHE {
275 s.svgs.remove(0);
276 }
277 let d = Rc::new(SvgDraw { doc: doc.clone(), color, fonts });
278 s.svgs.push((key, d.clone()));
279 d
280 }
281 };
282 let obj: DrawRef = d;
283 self.ltr_begin(r[0], r[2]);
284 self.cmd_custom(r[0], r[1], r[2], r[3], Some(obj), 0, key as u32);
285 self.ltr_end();
286 let ev = if id != 0 { self.behave(id, 0) } else { 0 };
287 let title = doc.title();
288 let label = if title.is_empty() { None } else { Some(title) };
289 if id != 0 || label.is_some() {
290 self.sem(K_IMAGE, id, label, 0);
291 }
292 ev
293 }
294}
295
296#[derive(Clone, Copy)]
300#[non_exhaustive]
301pub enum ViewImage<'a> {
302 Bitmap(&'a Rc<Surface>, u32),
305 Svg(&'a Rc<Svg>),
306}
307
308#[derive(Clone, Copy, Debug, Default, PartialEq)]
309enum Gesture {
310 #[default]
311 Idle,
312 Pan { mx: i32, my: i32, cx: f64, cy: f64, middle: bool },
314 Rect { x: i32, y: i32 },
316}
317
318#[derive(Clone)]
320pub struct ImageViewState {
321 pub zoom: f64,
323 pub cx: f64,
325 pub cy: f64,
326 pub fit: bool,
328 pub label: String,
330 pub gpu: CanvasGpu,
332 key: usize,
333 ver: u32,
334 conv: Option<Rc<Surface>>,
336 mips: Option<Rc<MipChain>>,
337 g: Gesture,
338 draw: Option<(u64, Rc<IvDraw>)>,
339 view: [i32; 2],
341 unit: f64,
342}
343
344impl Default for ImageViewState {
345 fn default() -> ImageViewState {
346 ImageViewState {
347 zoom: 1.0,
348 cx: 0.0,
349 cy: 0.0,
350 fit: true,
351 label: String::new(),
352 gpu: CanvasGpu::Zoom,
353 key: 0,
354 ver: 0,
355 conv: None,
356 mips: None,
357 g: Gesture::Idle,
358 draw: None,
359 view: [0, 0],
360 unit: 1.0,
361 }
362 }
363}
364
365impl core::fmt::Debug for ImageViewState {
366 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
367 f.debug_struct("ImageViewState").field("zoom", &self.zoom).field("cx", &self.cx).field("cy", &self.cy).field("fit", &self.fit).finish()
368 }
369}
370
371impl ImageViewState {
372 pub fn zoom_pct(&self) -> i32 {
374 (self.zoom * 100.0 + 0.5) as i32
375 }
376
377 pub fn set_fit(&mut self) {
379 self.fit = true;
380 }
381
382 pub fn set_actual(&mut self) {
384 self.fit = false;
385 self.zoom = 1.0;
386 }
387
388 pub fn zoom_by(&mut self, f: f64) {
390 if f > 0.0 {
391 self.fit = false;
392 self.zoom = (self.zoom * f).clamp(ZOOM_MIN, ZOOM_MAX);
393 }
394 }
395
396 pub fn zoom_to(&mut self, r: [f64; 4]) {
398 let (vw, vh) = (self.view[0] as f64, self.view[1] as f64);
399 if !(r[2] > 0.0 && r[3] > 0.0) || vw <= 0.0 || vh <= 0.0 {
400 return;
401 }
402 let u = if self.unit > 0.0 { self.unit } else { 1.0 };
403 self.fit = false;
404 self.zoom = (vw / (r[2] * u)).min(vh / (r[3] * u)).clamp(ZOOM_MIN, ZOOM_MAX);
405 self.cx = r[0] + r[2] * 0.5;
406 self.cy = r[1] + r[3] * 0.5;
407 }
408
409 fn step(&mut self, dir: i32) {
411 let z = self.zoom;
412 let next = if dir > 0 { STEPS.iter().copied().find(|&s| s > z * 1.001) } else { STEPS.iter().rev().copied().find(|&s| s < z * 0.999) };
413 if let Some(n) = next {
414 self.fit = false;
415 self.zoom = n;
416 }
417 }
418
419 fn zoom_at(&mut self, z: f64, dx: f64, dy: f64) {
422 let s0 = self.zoom * self.unit;
423 let z = z.clamp(ZOOM_MIN, ZOOM_MAX);
424 let s1 = z * self.unit;
425 if !(s0 > 0.0 && s1 > 0.0) {
426 return;
427 }
428 let (px, py) = (self.cx + dx / s0, self.cy + dy / s0);
429 self.fit = false;
430 self.zoom = z;
431 self.cx = px - dx / s1;
432 self.cy = py - dy / s1;
433 }
434}
435
436struct IvDraw {
438 img: IvImg,
439 mips: Option<Rc<MipChain>>,
440 s: f64,
441 ox: f64,
442 oy: f64,
443 iw: f64,
444 ih: f64,
445 flags: u32,
446 bg: u32,
447 checker: Option<(i32, u32, u32)>,
448 grid: u32,
449 color: u32,
450 fonts: Rc<SvgFonts>,
451}
452
453enum IvImg {
454 Bitmap(Rc<Surface>),
455 Svg(Rc<Svg>),
456}
457
458impl IvDraw {
459 fn bitmap(&self, g: &mut Gfx, src0: &Surface, a: [i32; 4], v: CanvasView) {
461 let mut src = src0;
462 let mut s = v.scale;
463 let mut level = 1;
464 if let Some(m) = &self.mips {
465 while s < 0.5 {
466 match m.level(level) {
467 Some(l) => {
468 src = l;
469 s *= 2.0;
470 level += 1;
471 }
472 None => break,
473 }
474 }
475 }
476 let sx = src.w as f64 / src0.w.max(1) as f64; let sy = src.h as f64 / src0.h.max(1) as f64;
478 let filter = if v.scale >= NEAREST_FROM && self.flags & IV_SMOOTH == 0 { RS_NEAREST } else { RS_BILINEAR };
479 let w = a[2] - a[0];
480 const BAND: i32 = 64;
481 let mut tile = Surface::new(w, BAND, FMT_PARGB);
482 if tile.px.is_empty() {
483 return;
484 }
485 let stride = g.sf.stride as usize;
486 let mut y = a[1];
487 while y < a[3] {
488 let n = (a[3] - y).min(BAND);
489 let m = Matrix { a: sx / v.scale, b: 0.0, c: (a[0] as f64 - v.ox) * sx / v.scale, d: 0.0, e: sy / v.scale, f: (y as f64 - v.oy) * sy / v.scale };
491 if affine(src, &mut tile, &m, filter, XF_TRANSPARENT, Some([0, 0, w, n])) != 0 {
492 return;
493 }
494 for r in 0..n {
495 let to = r as usize * tile.stride as usize;
496 let trow = &tile.px[to..to + w as usize * 4];
497 let d = (y + r) as usize * stride + a[0] as usize * 4;
498 let Some(drow) = g.sf.px.get_mut(d..d + w as usize * 4) else { continue };
499 match self.checker {
500 Some((cell, c0, c1)) => {
501 let cfg = gfx::straight::ViewCfg { cell, c0, c1 };
502 gfx::straight::view_row_pm(drow, trow, a[0] - v.ox as i32, y + r - v.oy as i32, &cfg);
503 }
504 None => gfx::blend::blit_over(drow, trow, 255),
505 }
506 }
507 y += n;
508 }
509 }
510
511 fn checker_fill(&self, g: &mut Gfx, a: [i32; 4], v: CanvasView) {
513 let Some((cell, c0, c1)) = self.checker else { return };
514 let cfg = gfx::straight::ViewCfg { cell, c0, c1 };
515 let stride = g.sf.stride as usize;
516 for y in a[1]..a[3] {
517 let d = y as usize * stride + a[0] as usize * 4;
518 let Some(row) = g.sf.px.get_mut(d..d + (a[2] - a[0]) as usize * 4) else { continue };
519 for (i, px) in row.chunks_exact_mut(4).enumerate() {
520 let c = gfx::straight::view_bg(a[0] + i as i32 - v.ox as i32, y - v.oy as i32, &cfg);
521 px.copy_from_slice(&c.to_le_bytes());
522 }
523 }
524 }
525
526 fn grid(&self, g: &mut Gfx, a: [i32; 4], v: CanvasView) {
528 let (dx, dy) = (g.dc.ox, g.dc.oy);
529 g.dc.set_color(self.grid);
530 let i0 = ((a[0] as f64 - v.ox) / v.scale).max(0.0) as i64;
531 let i1 = (((a[2] as f64 - v.ox) / v.scale) as i64 + 1).min(self.iw as i64);
532 for i in i0..=i1 {
533 let x = (v.ox + i as f64 * v.scale).rnd() as i32;
534 if x >= a[0] && x < a[2] {
535 g.dc.fill(g.sf, x - dx, a[1] - dy, 1, a[3] - a[1]);
536 }
537 }
538 let j0 = ((a[1] as f64 - v.oy) / v.scale).max(0.0) as i64;
539 let j1 = (((a[3] as f64 - v.oy) / v.scale) as i64 + 1).min(self.ih as i64);
540 for j in j0..=j1 {
541 let y = (v.oy + j as f64 * v.scale).rnd() as i32;
542 if y >= a[1] && y < a[3] {
543 g.dc.fill(g.sf, a[0] - dx, y - dy, a[2] - a[0], 1);
544 }
545 }
546 }
547}
548
549impl Draw for IvDraw {
550 fn draw(&self, g: &mut Gfx, r: [i32; 4]) {
551 self.paint(g, r, CanvasView { scale: self.s, ox: self.ox, oy: self.oy });
552 }
553}
554
555impl CanvasContent for IvDraw {
556 fn draw_view(&self, g: &mut Gfx, v: CanvasView, rect: [i32; 4]) {
557 self.paint(g, rect, v);
558 }
559}
560
561impl IvDraw {
562 fn paint(&self, g: &mut Gfx, r: [i32; 4], v: CanvasView) {
564 let (dx, dy) = (g.dc.ox, g.dc.oy);
565 let clip = g.dc.clip;
566 let view = [r[0] + dx, r[1] + dy, r[0] + dx + r[2], r[1] + dy + r[3]];
567 let k = [clip[0].max(view[0]), clip[1].max(view[1]), clip[2].min(view[2]), clip[3].min(view[3])];
568 if k[2] <= k[0] || k[3] <= k[1] {
569 return;
570 }
571 g.dc.set_color(self.bg);
573 g.dc.fill(g.sf, k[0] - dx, k[1] - dy, k[2] - k[0], k[3] - k[1]);
574 let ir = [
576 v.ox.max(-1.0e9) as i64,
577 v.oy.max(-1.0e9) as i64,
578 (v.ox + self.iw * v.scale).min(1.0e9).ceil_i64(),
579 (v.oy + self.ih * v.scale).min(1.0e9).ceil_i64(),
580 ];
581 let a = [
582 (ir[0].max(k[0] as i64)) as i32,
583 (ir[1].max(k[1] as i64)) as i32,
584 (ir[2].min(k[2] as i64)) as i32,
585 (ir[3].min(k[3] as i64)) as i32,
586 ];
587 if a[2] <= a[0] || a[3] <= a[1] {
588 return;
589 }
590 match &self.img {
591 IvImg::Bitmap(sf) => {
592 self.bitmap(g, sf, a, v);
593 if v.scale >= GRID_FROM && self.flags & IV_NO_GRID == 0 {
594 self.grid(g, a, v);
595 }
596 }
597 IvImg::Svg(doc) => {
598 self.checker_fill(g, a, v);
599 let mut th = SvgText { text: &mut *g.text, fonts: &self.fonts };
600 let mut o = Opts { color: self.color, text: Some(&mut th) };
601 let m = Matrix { a: v.scale, b: 0.0, c: v.ox, d: 0.0, e: v.scale, f: v.oy };
602 doc.render(g.sf, &m, Some(a), &mut o);
603 }
604 }
605 }
606}
607
608trait Round {
610 fn ceil_i64(self) -> i64;
611 fn rnd(self) -> f64;
612}
613
614impl Round for f64 {
615 fn ceil_i64(self) -> i64 {
616 let t = self as i64;
617 if (t as f64) < self { t + 1 } else { t }
618 }
619 fn rnd(self) -> f64 {
621 if !(self.abs() < 4.0e15) {
622 return self;
623 }
624 let v = self + 0.5;
625 let t = v as i64 as f64;
626 if t > v { t - 1.0 } else { t }
627 }
628}
629
630impl Ui {
631 fn iv_key(&mut self, vk: u32, need: u32, mask: u32) -> bool {
634 let rtl = self.rtl;
635 for k in self.wp.ctx.keyq.iter_mut() {
636 if (*k as i32) < 0 {
637 continue;
638 }
639 let mods = (*k >> 16) & 0x7FFF;
640 let mut v = *k & 0xFFFF;
641 if rtl && mods & (MOD_CTRL | MOD_ALT) == 0 {
642 v = match v {
643 VK_LEFT => VK_RIGHT,
644 VK_RIGHT => VK_LEFT,
645 v => v,
646 };
647 }
648 if v == vk && mods & mask == need {
649 *k |= 0x8000_0000;
650 return true;
651 }
652 }
653 false
654 }
655
656 #[cfg_attr(jfgui_dev, track_caller)]
659 pub fn image_view(&mut self, id: u32, st: &mut ImageViewState, img: ViewImage, flags: u32) -> u32 {
660 let _src = self.src_scope();
661 let _ = self.take_flags();
662 if self.wp.lay.next_w == 0 && self.wp.lay.next_h == 0 && self.wp.lay.next_rect_set == 0 {
663 self.next_size(-1, -1);
664 }
665 let r = self.place(0, 0);
666 let dpi = self.wp.rt.dpi;
667 let (key, ver, iw, ih, unit) = match img {
669 ViewImage::Bitmap(sf, v) => (Rc::as_ptr(sf) as usize, v, sf.w.max(0) as f64, sf.h.max(0) as f64, 1.0),
670 ViewImage::Svg(d) => {
671 let (w, h) = d.size();
672 (Rc::as_ptr(d) as usize, 0, w, h, dpi as f64 / 96.0)
673 }
674 };
675 if key != st.key {
676 if st.key != 0 {
677 st.fit = true;
678 }
679 st.key = key;
680 st.ver = ver.wrapping_add(1); st.g = Gesture::Idle;
682 }
683 if let ViewImage::Bitmap(sf, v) = img {
684 if st.ver != v {
685 st.ver = v;
686 st.conv = None;
687 st.mips = None;
688 if sf.fmt != FMT_PARGB && sf.fmt != FMT_ARGB && sf.w > 0 && sf.h > 0 {
689 let mut c = Surface::new(sf.w, sf.h, FMT_PARGB);
691 let n = c.px.len();
692 if n > 0 && c.stride == sf.stride && sf.px.len() >= n {
693 c.px.copy_from_slice(&sf.px[..n]);
694 for p in c.px.chunks_exact_mut(4) {
695 p[3] = 255;
696 }
697 st.conv = Some(Rc::new(c));
698 }
699 }
700 }
701 } else {
702 st.conv = None;
703 st.mips = None;
704 }
705 st.unit = unit;
706 st.view = [r[2].max(0), r[3].max(0)];
707 let (vw, vh) = (r[2].max(1) as f64, r[3].max(1) as f64);
708 let has = iw > 0.0 && ih > 0.0;
709 let fitz = if has { (vw / (iw * unit)).min(vh / (ih * unit)).clamp(ZOOM_MIN, ZOOM_MAX) } else { 1.0 };
710 let view0 = (st.zoom, st.cx, st.cy, st.fit);
711 let mut cur = CUR_MOVE;
713 if matches!(st.g, Gesture::Rect { .. }) || self.mods & MOD_CTRL != 0 {
714 cur = CUR_CROSS;
715 }
716 let ev = self.behave(id, HB_FOCUS | HB_MOVES | HB_GRAB_H | HB_GRAB_V | cur << HB_CURSOR_SHIFT);
717 let state = self.wp.ctx.last_state;
718 let (mx, my) = (self.rtl_map(self.wp.ctx.mx, r[0], r[2]), self.wp.ctx.my);
719 let (cxv, cyv) = (r[0] as f64 + vw * 0.5, r[1] as f64 + vh * 0.5);
720 if st.fit {
721 st.zoom = fitz;
722 st.cx = iw * 0.5;
723 st.cy = ih * 0.5;
724 }
725 st.zoom = st.zoom.clamp(ZOOM_MIN, ZOOM_MAX);
726 if has {
727 let c = &self.wp.ctx;
729 let (wx, wy) = if ev & EV_HOVER != 0 { (c.wheel_x, c.wheel_y) } else { (0, 0) };
730 if wx != 0 || wy != 0 {
731 let ctrl = self.mods & MOD_CTRL != 0;
732 let shift = self.mods & MOD_SHIFT != 0;
733 let zoom = if flags & IV_WHEEL_SCROLL != 0 { ctrl } else { !shift };
734 let s = st.zoom * unit;
735 let notch = self.dp(48) as f64;
736 if zoom && wy != 0 {
737 let f = rt::x87::pow(1.25, wy as f64 / 120.0);
738 st.zoom_at(st.zoom * f, mx as f64 + 0.5 - cxv, my as f64 + 0.5 - cyv);
739 } else {
740 let (mut ddx, mut ddy) = (wx as f64 / 120.0 * notch, -(wy as f64) / 120.0 * notch);
742 if shift {
743 ddx += ddy;
744 ddy = 0.0;
745 }
746 if self.rtl {
747 ddx = -ddx;
748 }
749 st.fit = false;
750 st.cx += ddx / s;
751 st.cy += ddy / s;
752 }
753 if zoom && wx != 0 {
754 st.fit = false;
755 st.cx += wx as f64 / 120.0 * notch / s;
756 }
757 let c = &mut self.wp.ctx;
758 c.wheel_x = 0;
759 c.wheel_y = 0;
760 }
761 let c = &self.wp.ctx;
763 let hot = c.hot == id;
764 let s = st.zoom * unit;
765 if hot && c.pressed & (1 << BTN_LEFT) != 0 && c.active == id {
766 st.g = if self.mods & MOD_CTRL != 0 { Gesture::Rect { x: mx, y: my } } else { Gesture::Pan { mx, my, cx: st.cx, cy: st.cy, middle: false } };
767 } else if hot && c.pressed & (1 << BTN_MIDDLE) != 0 {
768 st.g = Gesture::Pan { mx, my, cx: st.cx, cy: st.cy, middle: true };
769 }
770 let c = &self.wp.ctx;
771 match st.g {
772 Gesture::Pan { mx: x0, my: y0, cx, cy, middle } => {
773 let held = if middle { c.mbtn & (1 << BTN_MIDDLE) != 0 } else { c.active == id && c.mbtn & (1 << BTN_LEFT) != 0 };
774 if held {
775 if mx != x0 || my != y0 {
776 st.fit = false;
777 st.cx = cx - (mx - x0) as f64 / s;
778 st.cy = cy - (my - y0) as f64 / s;
779 }
780 } else {
781 st.g = Gesture::Idle;
782 }
783 }
784 Gesture::Rect { x, y } => {
785 if !(c.active == id && c.mbtn & (1 << BTN_LEFT) != 0) {
786 st.g = Gesture::Idle;
787 let lim = self.dp(4);
788 if (mx - x).abs() > lim && (my - y).abs() > lim {
789 let ox = cxv - st.cx * s;
791 let oy = cyv - st.cy * s;
792 let (x0, x1) = (x.min(mx) as f64, x.max(mx) as f64);
793 let (y0, y1) = (y.min(my) as f64, y.max(my) as f64);
794 st.zoom_to([(x0 - ox) / s, (y0 - oy) / s, (x1 - x0) / s, (y1 - y0) / s]);
795 }
796 }
797 }
798 Gesture::Idle => {}
799 }
800 if ev & EV_DOUBLE != 0 {
802 st.g = Gesture::Idle;
803 if st.fit || (st.zoom - 1.0).abs() > 1e-9 {
804 st.zoom_at(1.0, mx as f64 + 0.5 - cxv, my as f64 + 0.5 - cyv);
805 } else {
806 st.fit = true;
807 }
808 }
809 if self.wp.ctx.focus == id {
811 let s = st.zoom * unit;
812 let (px, py) = (vw / 8.0 / s, vh / 8.0 / s);
813 let mut moved = false;
814 for (vk, ddx, ddy) in [(VK_LEFT, -px, 0.0), (VK_RIGHT, px, 0.0), (VK_UP, 0.0, -py), (VK_DOWN, 0.0, py)] {
815 if self.iv_key(vk, 0, MOD_CTRL | MOD_ALT) {
816 st.cx += ddx;
817 st.cy += ddy;
818 moved = true;
819 }
820 }
821 let page = vh * 0.9 / s;
822 if self.iv_key(VK_PRIOR, 0, MOD_CTRL | MOD_ALT) {
823 st.cy -= page;
824 moved = true;
825 }
826 if self.iv_key(VK_NEXT, 0, MOD_CTRL | MOD_ALT) {
827 st.cy += page;
828 moved = true;
829 }
830 if self.iv_key(VK_HOME, 0, MOD_ALT) {
831 st.cy = f64::MIN / 4.0;
832 moved = true;
833 }
834 if self.iv_key(VK_END, 0, MOD_ALT) {
835 st.cy = f64::MAX / 4.0;
836 moved = true;
837 }
838 if moved {
839 st.fit = false;
840 self.wp.ctx.focus_visible = true;
841 }
842 for (vk, dir) in [(VK_OEM_PLUS, 1), (VK_ADD, 1), (VK_OEM_MINUS, -1), (VK_SUBTRACT, -1)] {
843 if self.iv_key(vk, 0, MOD_ALT) {
844 st.step(dir);
845 }
846 }
847 if self.iv_key(VK_0, MOD_CTRL, MOD_CTRL | MOD_ALT) || self.iv_key(VK_NUMPAD0, MOD_CTRL, MOD_CTRL | MOD_ALT) {
848 st.fit = true;
849 }
850 if self.iv_key(VK_1, MOD_CTRL, MOD_CTRL | MOD_ALT) || self.iv_key(VK_NUMPAD0 + 1, MOD_CTRL, MOD_CTRL | MOD_ALT) {
851 st.set_actual();
852 }
853 }
854 }
855 if st.fit {
856 st.zoom = fitz;
857 st.cx = iw * 0.5;
858 st.cy = ih * 0.5;
859 }
860 let s = st.zoom * unit;
862 let clampc = |c: f64, len: f64, v: f64| -> f64 {
863 let half = v * 0.5 / s;
864 if len * s <= v { len * 0.5 } else { c.clamp(half, len - half) }
865 };
866 if has && s > 0.0 {
867 st.cx = clampc(st.cx, iw, vw);
868 st.cy = clampc(st.cy, ih, vh);
869 }
870 let ox = (cxv - st.cx * s).rnd();
872 let oy = (cyv - st.cy * s).rnd();
873 if let ViewImage::Bitmap(sf, _) = img {
874 let base: &Surface = st.conv.as_deref().unwrap_or(sf);
876 if s < 0.5 && st.mips.is_none() && base.w > 1 && base.h > 1 {
877 if let Some(mut m) = MipChain::alloc(base.w, base.h, 0) {
878 if m.update(base, None) == 0 {
879 st.mips = Some(Rc::new(m));
880 }
881 }
882 }
883 }
884 let dark = self.theme_dark;
885 let hc = self.high_contrast();
886 let bg = self.color(if hc { T_BG } else { T_BG_ALT });
887 let checker = if flags & IV_NO_CHECKER != 0 || hc {
888 None
889 } else if dark {
890 Some((self.dp(8), 0x2B2B2B, 0x3A3A3A))
891 } else {
892 Some((self.dp(8), 0xFFFFFF, 0xE3E3E3))
893 };
894 let grid = if hc {
895 self.color(T_TEXT) & 0xFF_FFFF | 0x9000_0000
896 } else if dark {
897 0x60FF_FFFF
898 } else {
899 0x5000_0000
900 };
901 let color = if flags & IV_TEXT_COLOR != 0 { self.color(T_TEXT) } else { 0xFF00_0000 };
902 let fonts = self.svg_fonts();
903 let mut h = Fnv::new();
905 h.u64(key as u64 | (st.ver as u64) << 48);
906 h.u64(flags as u64 | (bg as u64) << 32);
907 h.u64(checker.map_or(0, |c| c.0 as u64 | (c.1 as u64) << 16 | (c.2 as u64) << 40));
908 h.u64(grid as u64 | (color as u64) << 32);
909 h.u64(Rc::as_ptr(&fonts) as usize as u64 ^ st.mips.as_ref().map_or(0, |m| Rc::as_ptr(m) as usize as u64));
910 let content = h.0;
911 h.u64(s.to_bits());
912 h.u64(ox.to_bits());
913 h.u64(oy.to_bits());
914 let hash = h.0;
915 let obj = match &st.draw {
916 Some((k, d)) if *k == hash => d.clone(),
917 _ => {
918 let image = match img {
919 ViewImage::Bitmap(sf, _) => IvImg::Bitmap(st.conv.clone().unwrap_or_else(|| sf.clone())),
920 ViewImage::Svg(d) => IvImg::Svg(d.clone()),
921 };
922 let d = Rc::new(IvDraw {
923 img: image,
924 mips: st.mips.clone(),
925 s,
926 ox,
927 oy,
928 iw,
929 ih,
930 flags,
931 bg,
932 checker,
933 grid,
934 color,
935 fonts,
936 });
937 st.draw = Some((hash, d.clone()));
938 d
939 }
940 };
941 self.ltr_begin(r[0], r[2]);
942 self.clip_push(r[0], r[1], r[2], r[3]);
943 self.cmd_custom(r[0], r[1], r[2], r[3], Some(obj.clone()), 0, hash as u32);
944 if has {
945 let (view, ver, gpu) = (CanvasView { scale: s, ox, oy }, (content ^ content >> 32) as u32, st.gpu);
946 self.canvas_gpu(CanvasDesc { id, name: "image", rect: r, view, ver, content: obj, gpu, exact_pan: false });
947 }
948 if has && flags & IV_NO_BARS == 0 {
950 let sty = self.style;
951 let t = sty.scrollbar_thickness(self);
952 let (cw, ch) = ((iw * s).ceil_i64().min(i32::MAX as i64 / 2) as i32, (ih * s).ceil_i64().min(i32::MAX as i64 / 2) as i32);
953 let (bw, bh) = (r[2], r[3]);
954 let vneed = ch > bh;
955 let hneed = cw > bw;
956 if vneed {
957 let mut pos = (st.cy * s - vh * 0.5).rnd() as i32;
958 self.next_rect_px(r[0] + r[2] - t, r[1], t, if hneed { bh - t } else { bh });
959 if self.scrollbar(part(id, 1), &mut pos, ch, bh, true) & EV_ACT != 0 {
960 st.fit = false;
961 st.cy = (pos as f64 + vh * 0.5) / s;
962 }
963 }
964 if hneed {
965 let mut pos = (st.cx * s - vw * 0.5).rnd() as i32;
966 self.next_rect_px(r[0], r[1] + r[3] - t, if vneed { bw - t } else { bw }, t);
967 if self.scrollbar(part(id, 2), &mut pos, cw, bw, false) & EV_ACT != 0 {
968 st.fit = false;
969 st.cx = (pos as f64 + vw * 0.5) / s;
970 }
971 }
972 }
973 if let Gesture::Rect { x, y } = st.g {
975 let (x0, y0) = (x.min(mx), y.min(my));
976 let (w, hh) = ((x - mx).abs(), (y - my).abs());
977 let acc = self.color(T_ACCENT);
978 self.draw_color(acc & 0xFF_FFFF | 0x3000_0000);
979 self.draw_rect(x0, y0, w, hh);
980 self.draw_color(acc);
981 self.draw_rect(x0, y0, w, 1);
982 self.draw_rect(x0, y0 + hh - 1, w, 1);
983 self.draw_rect(x0, y0, 1, hh);
984 self.draw_rect(x0 + w - 1, y0, 1, hh);
985 }
986 self.clip_pop();
987 self.ltr_end();
988 self.wp.ctx.last_rect = r;
990 self.wp.ctx.last_state = state;
991 self.focus_ring_draw(ev);
992 let label = if !st.label.is_empty() {
993 st.label.clone()
994 } else {
995 match img {
996 ViewImage::Svg(d) if !d.title().is_empty() => String::from(d.title()),
997 _ => String::from(tr("Bild")),
998 }
999 };
1000 self.wp.ctx.last_rect = r;
1001 self.wp.ctx.last_state = state;
1002 let changed = (st.zoom, st.cx, st.cy, st.fit) != view0;
1003 if changed {
1004 self.repass();
1005 }
1006 self.sem(K_IMAGE, id, Some(&label), st.zoom_pct());
1007 if changed { ev | EV_ACT } else { ev }
1008 }
1009}