refactor(editor): absorb the HSV color picker into jian ColorPicker
The floating color picker's rendering, layout, hit-test and HSV<->RGB math now live in jian ColorPicker; color_picker.rs shrinks to a wrapper that owns the property-panel-relative anchor (rect), the EditorState plumbing and the drag dispatch, re-exporting hsv_to_rgb / rgb_to_hsv / ColorPickerHit / PICKER_* so the hosts import them unchanged. ~330 fewer lines of OP-specific paint. Bumps vendor/jian to 9580247.
This commit is contained in:
parent
7040e5592a
commit
32ae68a248
|
|
@ -1,42 +1,23 @@
|
|||
//! Floating HSV colour picker — opens from a Fill / Stroke swatch
|
||||
//! click in the PropertyPanel. SV box + hue strip + R/G/B readout
|
||||
//! + hex; mirrors the layout of common design-tool pickers.
|
||||
//! Floating HSV colour picker — opens from a Fill / Stroke swatch click in
|
||||
//! the PropertyPanel. The rendering, layout, hit-test and HSV↔RGB math live
|
||||
//! in jian `components::color_picker`; this wrapper owns the
|
||||
//! property-panel-relative anchor, the `EditorState` plumbing, and the
|
||||
//! drag-dispatch glue.
|
||||
|
||||
use crate::theme::Theme;
|
||||
use crate::widgets::editor_state_ext::theme_for;
|
||||
use crate::widgets::icons::{draw_icon, Icon};
|
||||
use crate::widgets::icons::Icon;
|
||||
use crate::widgets::{PaintCx, Widget, WidgetId};
|
||||
use crate::{Color, Point2D, Rect, TextLayout};
|
||||
use crate::{Point2D, Rect};
|
||||
use jian_widgets::components::color_picker::ColorPicker as JianColorPicker;
|
||||
use op_editor_core::ui_draft::{ColorPickerDrag, ColorPickerState, ColorTarget};
|
||||
use op_editor_core::EditorState;
|
||||
|
||||
pub const PICKER_WIDTH: f32 = 240.0;
|
||||
pub const PICKER_HEIGHT: f32 = 336.0;
|
||||
|
||||
const PAD: f32 = 8.0;
|
||||
const HEADER_HEIGHT: f32 = 28.0;
|
||||
const SV_HEIGHT: f32 = 140.0;
|
||||
const ROW_GAP: f32 = 8.0;
|
||||
const HUE_HEIGHT: f32 = 12.0;
|
||||
const HUE_HANDLE_R: f32 = 7.0;
|
||||
const SV_HANDLE_R: f32 = 6.0;
|
||||
const SWATCH_SIZE: f32 = 28.0;
|
||||
const RGB_BOX_W: f32 = 56.0;
|
||||
const RGB_BOX_H: f32 = 32.0;
|
||||
const HEX_HEIGHT: f32 = 28.0;
|
||||
|
||||
/// What a press inside the picker did. The host translates these
|
||||
/// to a `ColorPickerDrag` it then keeps live until release.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ColorPickerHit {
|
||||
SvBox,
|
||||
HueSlider,
|
||||
Eyedropper,
|
||||
Close,
|
||||
/// Click inside the panel that doesn't hit a control — swallows
|
||||
/// to keep the picker open.
|
||||
Inside,
|
||||
}
|
||||
// Re-export jian's public surface so hosts (op-host-native / op-host-web) and
|
||||
// sibling widgets keep importing it from this module unchanged.
|
||||
pub use jian_widgets::components::color_picker::{
|
||||
hsv_to_rgb, rgb_to_hsv, ColorPickerHit, PICKER_HEIGHT, PICKER_WIDTH,
|
||||
};
|
||||
|
||||
pub struct ColorPicker {
|
||||
pub id: WidgetId,
|
||||
|
|
@ -56,10 +37,10 @@ impl ColorPicker {
|
|||
}
|
||||
}
|
||||
|
||||
/// Resolve the picker's anchor (top-left) in viewport coords —
|
||||
/// pinned near an explicit click point when available, otherwise
|
||||
/// to the right rail near the Fill section. Hosts use this for
|
||||
/// paint AND hit-test so they stay in sync.
|
||||
/// Resolve the picker's anchor (top-left) in viewport coords — pinned near
|
||||
/// an explicit click point when available, otherwise to the right rail near
|
||||
/// the Fill section. Hosts use this for paint AND hit-test so they stay in
|
||||
/// sync. (Domain-specific to OP's right rail, so it stays here.)
|
||||
pub fn rect(&self, viewport_w: f32, viewport_h: f32) -> Rect {
|
||||
let x = self
|
||||
.state
|
||||
|
|
@ -69,8 +50,6 @@ impl ColorPicker {
|
|||
let right_rail_left = viewport_w - self.property_panel_width;
|
||||
(right_rail_left - PICKER_WIDTH - 8.0).max(8.0)
|
||||
});
|
||||
// Center vertically around the swatch the user clicked, then
|
||||
// clamp so the panel stays fully on-screen.
|
||||
let mut y = self.state.anchor_y - PICKER_HEIGHT / 2.0;
|
||||
let top_min = crate::widgets::TOP_BAR_HEIGHT + 8.0;
|
||||
let bottom_max = (viewport_h - PICKER_HEIGHT - 8.0).max(top_min);
|
||||
|
|
@ -86,47 +65,19 @@ impl ColorPicker {
|
|||
}
|
||||
}
|
||||
|
||||
/// Map a press point inside the picker to a hit (or None if the
|
||||
/// point is outside the panel rect entirely).
|
||||
/// Map a press point to a hit, or `None` if outside the panel.
|
||||
pub fn hit_test(&self, panel: Rect, point: Point2D) -> Option<ColorPickerHit> {
|
||||
if !(panel).contains(point) {
|
||||
return None;
|
||||
}
|
||||
// Close chip sits visually on top of the SV box's top-right
|
||||
// corner — check it first so its presses don't get swallowed
|
||||
// by the SV drag handler.
|
||||
let close = close_rect(panel);
|
||||
if (close).contains(point) {
|
||||
return Some(ColorPickerHit::Close);
|
||||
}
|
||||
let sv = sv_rect(panel);
|
||||
if (sv).contains(point) {
|
||||
return Some(ColorPickerHit::SvBox);
|
||||
}
|
||||
let hue = hue_rect(panel);
|
||||
if (hue).contains(point) {
|
||||
return Some(ColorPickerHit::HueSlider);
|
||||
}
|
||||
let eye = eyedropper_rect(panel);
|
||||
if (eye).contains(point) {
|
||||
return Some(ColorPickerHit::Eyedropper);
|
||||
}
|
||||
Some(ColorPickerHit::Inside)
|
||||
JianColorPicker::hit(panel, point)
|
||||
}
|
||||
|
||||
/// Compute new HSV when dragging inside the SV box.
|
||||
/// New `(sat, val)` when dragging inside the SV box.
|
||||
pub fn sv_at(&self, panel: Rect, point: Point2D) -> (f32, f32) {
|
||||
let sv = sv_rect(panel);
|
||||
let x = ((point.x - sv.origin.x) / sv.size.x).clamp(0.0, 1.0);
|
||||
let y = ((point.y - sv.origin.y) / sv.size.y).clamp(0.0, 1.0);
|
||||
(x, 1.0 - y)
|
||||
JianColorPicker::sv_at(panel, point)
|
||||
}
|
||||
|
||||
/// Compute new hue when dragging across the hue strip.
|
||||
/// New hue when dragging across the hue strip.
|
||||
pub fn hue_at(&self, panel: Rect, point: Point2D) -> f32 {
|
||||
let h = hue_rect(panel);
|
||||
let t = ((point.x - h.origin.x) / h.size.x).clamp(0.0, 1.0);
|
||||
t * 360.0
|
||||
JianColorPicker::hue_at(panel, point)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +108,19 @@ impl Widget for ColorPicker {
|
|||
}
|
||||
|
||||
fn paint(&self, cx: &mut PaintCx<'_>, rect: Rect) {
|
||||
paint_picker(cx, &self.theme, &self.state, rect);
|
||||
JianColorPicker {
|
||||
hue: self.state.hue,
|
||||
sat: self.state.sat,
|
||||
val: self.state.val,
|
||||
title: target_label(self.state.target),
|
||||
eyedropper_icon: Icon::Pencil.paths(),
|
||||
close_icon: Icon::Close.paths(),
|
||||
}
|
||||
.paint(
|
||||
cx.backend,
|
||||
rect,
|
||||
&crate::widgets::button::tokens_from_theme(&self.theme),
|
||||
);
|
||||
}
|
||||
|
||||
fn access_node(&self) -> accesskit::Node {
|
||||
|
|
@ -167,337 +130,7 @@ impl Widget for ColorPicker {
|
|||
}
|
||||
}
|
||||
|
||||
fn paint_picker(cx: &mut PaintCx<'_>, theme: &Theme, state: &ColorPickerState, panel: Rect) {
|
||||
// Card background + outline.
|
||||
cx.backend.fill_round_rect(panel, 8.0, theme.card);
|
||||
cx.backend.stroke_round_rect(panel, 8.0, theme.border, 1.0);
|
||||
|
||||
// 0) Header row — picker title + close chip.
|
||||
let title = match state.target {
|
||||
ColorTarget::Fill => "Fill",
|
||||
ColorTarget::Stroke => "Stroke",
|
||||
ColorTarget::GradientStop(_) => "Gradient Stop",
|
||||
ColorTarget::EffectColor(_) => "Effect Color",
|
||||
};
|
||||
let title_layout = TextLayout::single_run(
|
||||
title,
|
||||
"system-ui",
|
||||
13.0,
|
||||
(theme.foreground).to_jian(),
|
||||
Point2D::new(0.0, 0.0),
|
||||
);
|
||||
cx.backend.draw_text(
|
||||
&title_layout,
|
||||
Point2D::new(panel.origin.x + PAD, header_top(panel) + 19.0),
|
||||
);
|
||||
|
||||
// 1) SV box — saturation × value gradient under the current hue.
|
||||
// Software-painted with a column-by-column gradient, then a
|
||||
// vertical black overlay. Quick + faithful enough.
|
||||
paint_sv_box(cx, sv_rect(panel), state.hue, state.sat, state.val);
|
||||
|
||||
// 2) Hue strip — 6-segment rainbow with a circular handle.
|
||||
paint_hue_strip(cx, hue_rect(panel), state.hue, theme);
|
||||
|
||||
// 3) Eyedropper + current swatch.
|
||||
let eye = eyedropper_rect(panel);
|
||||
draw_icon(
|
||||
cx.backend,
|
||||
Icon::Pencil,
|
||||
Point2D::new(eye.origin.x, eye.origin.y + 4.0),
|
||||
16.0,
|
||||
theme.muted_foreground,
|
||||
1.5,
|
||||
);
|
||||
let swatch = swatch_rect(panel);
|
||||
let cur = hsv_to_rgb(state.hue, state.sat, state.val);
|
||||
cx.backend.fill_round_rect(swatch, 14.0, cur);
|
||||
cx.backend
|
||||
.stroke_round_rect(swatch, 14.0, theme.border, 1.0);
|
||||
|
||||
// 4) R/G/B numeric boxes.
|
||||
let (r, g, b) = (
|
||||
(cur.r * 255.0).round() as u8,
|
||||
(cur.g * 255.0).round() as u8,
|
||||
(cur.b * 255.0).round() as u8,
|
||||
);
|
||||
let rgb_y = sv_rect(panel).origin.y + SV_HEIGHT + ROW_GAP + 36.0 + ROW_GAP;
|
||||
for (i, (label, value)) in [("R", r), ("G", g), ("B", b)].iter().enumerate() {
|
||||
let value = *value;
|
||||
let label = *label;
|
||||
let bx = panel.origin.x + PAD + (i as f32) * (RGB_BOX_W + 8.0);
|
||||
let box_rect = Rect {
|
||||
origin: Point2D::new(bx, rgb_y),
|
||||
size: Point2D::new(RGB_BOX_W, RGB_BOX_H),
|
||||
};
|
||||
cx.backend
|
||||
.stroke_round_rect(box_rect, 6.0, theme.border, 1.0);
|
||||
let val_str = value.to_string();
|
||||
let text_w = cx.backend.measure_text(&val_str, 14.0);
|
||||
let text = TextLayout::single_run(
|
||||
&val_str,
|
||||
"system-ui",
|
||||
14.0,
|
||||
(theme.foreground).to_jian(),
|
||||
Point2D::new(0.0, 0.0),
|
||||
);
|
||||
cx.backend.draw_text(
|
||||
&text,
|
||||
Point2D::new(bx + (RGB_BOX_W - text_w) / 2.0, rgb_y + 21.0),
|
||||
);
|
||||
let lab_w = cx.backend.measure_text(label, 12.0);
|
||||
let lab = TextLayout::single_run(
|
||||
label,
|
||||
"system-ui",
|
||||
12.0,
|
||||
(theme.muted_foreground).to_jian(),
|
||||
Point2D::new(0.0, 0.0),
|
||||
);
|
||||
cx.backend.draw_text(
|
||||
&lab,
|
||||
Point2D::new(bx + (RGB_BOX_W - lab_w) / 2.0, rgb_y + RGB_BOX_H + 14.0),
|
||||
);
|
||||
}
|
||||
|
||||
// 5) Hex bar at the bottom.
|
||||
let hex_rect = Rect {
|
||||
origin: Point2D::new(
|
||||
panel.origin.x + PAD,
|
||||
panel.origin.y + PICKER_HEIGHT - PAD - HEX_HEIGHT,
|
||||
),
|
||||
size: Point2D::new(PICKER_WIDTH - PAD * 2.0, HEX_HEIGHT),
|
||||
};
|
||||
cx.backend.fill_round_rect(hex_rect, 6.0, theme.muted);
|
||||
cx.backend
|
||||
.stroke_round_rect(hex_rect, 6.0, theme.primary, 1.0);
|
||||
let mini = Rect {
|
||||
origin: Point2D::new(hex_rect.origin.x + 6.0, hex_rect.origin.y + 6.0),
|
||||
size: Point2D::new(16.0, 16.0),
|
||||
};
|
||||
cx.backend.fill_round_rect(mini, 3.0, cur);
|
||||
cx.backend.stroke_round_rect(mini, 3.0, theme.border, 1.0);
|
||||
let hex_str = format!("#{:02x}{:02x}{:02x}", r, g, b);
|
||||
let hex_layout = TextLayout::single_run(
|
||||
&hex_str,
|
||||
"system-ui",
|
||||
13.0,
|
||||
(theme.foreground).to_jian(),
|
||||
Point2D::new(0.0, 0.0),
|
||||
);
|
||||
cx.backend.draw_text(
|
||||
&hex_layout,
|
||||
Point2D::new(hex_rect.origin.x + 32.0, hex_rect.origin.y + 19.0),
|
||||
);
|
||||
|
||||
// 6) Close (×) chip — filled hover-card so it actually reads
|
||||
// against the SV gradient behind it.
|
||||
let close = close_rect(panel);
|
||||
let chip_bg = Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.45,
|
||||
};
|
||||
cx.backend.fill_round_rect(close, 11.0, chip_bg);
|
||||
cx.backend.stroke_round_rect(close, 11.0, theme.border, 1.0);
|
||||
draw_icon(
|
||||
cx.backend,
|
||||
Icon::Close,
|
||||
Point2D::new(close.origin.x + 4.0, close.origin.y + 4.0),
|
||||
14.0,
|
||||
Color::WHITE,
|
||||
1.8,
|
||||
);
|
||||
|
||||
// SV handle and hue handle on top of everything.
|
||||
paint_sv_handle(cx, sv_rect(panel), state, theme);
|
||||
paint_hue_handle(cx, hue_rect(panel), state.hue, theme);
|
||||
}
|
||||
|
||||
fn paint_sv_box(cx: &mut PaintCx<'_>, sv: Rect, hue: f32, _sat: f32, _val: f32) {
|
||||
// Column gradient: at value=1, the row interpolates from white
|
||||
// (sat=0) to the hue's pure colour (sat=1); a vertical overlay
|
||||
// darkens to black as value drops to 0. Approximate with N=18
|
||||
// column strips for cheap painting.
|
||||
const STEPS_X: u32 = 18;
|
||||
const STEPS_Y: u32 = 14;
|
||||
let pure = hsv_to_rgb(hue, 1.0, 1.0);
|
||||
let cell_w = sv.size.x / STEPS_X as f32;
|
||||
let cell_h = sv.size.y / STEPS_Y as f32;
|
||||
for ix in 0..STEPS_X {
|
||||
for iy in 0..STEPS_Y {
|
||||
let s = (ix as f32 + 0.5) / STEPS_X as f32;
|
||||
let v = 1.0 - (iy as f32 + 0.5) / STEPS_Y as f32;
|
||||
let r = (1.0 - s) * 1.0 + s * pure.r;
|
||||
let g = (1.0 - s) * 1.0 + s * pure.g;
|
||||
let b = (1.0 - s) * 1.0 + s * pure.b;
|
||||
let color = Color {
|
||||
r: r * v,
|
||||
g: g * v,
|
||||
b: b * v,
|
||||
a: 1.0,
|
||||
};
|
||||
let cell = Rect {
|
||||
origin: Point2D::new(
|
||||
sv.origin.x + ix as f32 * cell_w,
|
||||
sv.origin.y + iy as f32 * cell_h,
|
||||
),
|
||||
size: Point2D::new(cell_w + 0.5, cell_h + 0.5),
|
||||
};
|
||||
cx.backend.fill_rect(cell, color);
|
||||
}
|
||||
}
|
||||
cx.backend.stroke_round_rect(
|
||||
sv,
|
||||
4.0,
|
||||
Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.25,
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
|
||||
fn paint_hue_strip(cx: &mut PaintCx<'_>, hue: Rect, _current: f32, _theme: &Theme) {
|
||||
const STEPS: u32 = 32;
|
||||
let cell_w = hue.size.x / STEPS as f32;
|
||||
for i in 0..STEPS {
|
||||
let h = (i as f32 + 0.5) / STEPS as f32 * 360.0;
|
||||
let c = hsv_to_rgb(h, 1.0, 1.0);
|
||||
let cell = Rect {
|
||||
origin: Point2D::new(hue.origin.x + i as f32 * cell_w, hue.origin.y),
|
||||
size: Point2D::new(cell_w + 0.5, hue.size.y),
|
||||
};
|
||||
cx.backend.fill_round_rect(cell, hue.size.y / 2.0, c);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_sv_handle(cx: &mut PaintCx<'_>, sv: Rect, state: &ColorPickerState, theme: &Theme) {
|
||||
let x = sv.origin.x + state.sat * sv.size.x;
|
||||
let y = sv.origin.y + (1.0 - state.val) * sv.size.y;
|
||||
let ring = Rect {
|
||||
origin: Point2D::new(x - SV_HANDLE_R, y - SV_HANDLE_R),
|
||||
size: Point2D::new(SV_HANDLE_R * 2.0, SV_HANDLE_R * 2.0),
|
||||
};
|
||||
cx.backend.stroke_oval(ring, theme.background, 3.0);
|
||||
cx.backend.stroke_oval(ring, Color::WHITE, 1.5);
|
||||
}
|
||||
|
||||
fn paint_hue_handle(cx: &mut PaintCx<'_>, hue: Rect, current_hue: f32, theme: &Theme) {
|
||||
let cx0 = hue.origin.x + (current_hue / 360.0).clamp(0.0, 1.0) * hue.size.x;
|
||||
let cy0 = hue.origin.y + hue.size.y / 2.0;
|
||||
let ring = Rect {
|
||||
origin: Point2D::new(cx0 - HUE_HANDLE_R, cy0 - HUE_HANDLE_R),
|
||||
size: Point2D::new(HUE_HANDLE_R * 2.0, HUE_HANDLE_R * 2.0),
|
||||
};
|
||||
let center = Rect {
|
||||
origin: Point2D::new(cx0 - 5.0, cy0 - 5.0),
|
||||
size: Point2D::new(10.0, 10.0),
|
||||
};
|
||||
cx.backend
|
||||
.fill_oval(center, hsv_to_rgb(current_hue, 1.0, 1.0));
|
||||
cx.backend.stroke_oval(ring, theme.background, 1.0);
|
||||
cx.backend.stroke_oval(ring, Color::WHITE, 2.0);
|
||||
}
|
||||
|
||||
fn header_top(panel: Rect) -> f32 {
|
||||
panel.origin.y + PAD
|
||||
}
|
||||
|
||||
fn sv_rect(panel: Rect) -> Rect {
|
||||
Rect {
|
||||
origin: Point2D::new(panel.origin.x + PAD, header_top(panel) + HEADER_HEIGHT),
|
||||
size: Point2D::new(PICKER_WIDTH - PAD * 2.0, SV_HEIGHT),
|
||||
}
|
||||
}
|
||||
|
||||
fn hue_rect(panel: Rect) -> Rect {
|
||||
let y = sv_rect(panel).origin.y + SV_HEIGHT + ROW_GAP + 8.0;
|
||||
let x = panel.origin.x + PAD + SWATCH_SIZE + 24.0 + 8.0;
|
||||
let w = PICKER_WIDTH - PAD * 2.0 - SWATCH_SIZE - 24.0 - 8.0;
|
||||
Rect {
|
||||
origin: Point2D::new(x, y),
|
||||
size: Point2D::new(w, HUE_HEIGHT),
|
||||
}
|
||||
}
|
||||
|
||||
fn eyedropper_rect(panel: Rect) -> Rect {
|
||||
let y = sv_rect(panel).origin.y + SV_HEIGHT + ROW_GAP + 6.0;
|
||||
Rect {
|
||||
origin: Point2D::new(panel.origin.x + PAD, y),
|
||||
size: Point2D::new(20.0, 20.0),
|
||||
}
|
||||
}
|
||||
|
||||
fn swatch_rect(panel: Rect) -> Rect {
|
||||
let y = sv_rect(panel).origin.y + SV_HEIGHT + ROW_GAP;
|
||||
Rect {
|
||||
origin: Point2D::new(panel.origin.x + PAD + 24.0, y),
|
||||
size: Point2D::new(SWATCH_SIZE, SWATCH_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
/// Close chip — pinned to the header row above the SV box so it
|
||||
/// doesn't steal clicks meant for the colour gradient.
|
||||
fn close_rect(panel: Rect) -> Rect {
|
||||
Rect {
|
||||
origin: Point2D::new(
|
||||
panel.origin.x + PICKER_WIDTH - PAD - 22.0,
|
||||
header_top(panel) + (HEADER_HEIGHT - 22.0) / 2.0,
|
||||
),
|
||||
size: Point2D::new(22.0, 22.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// HSV → RGB, h 0..360, s/v 0..1. Returns alpha 1.
|
||||
pub fn hsv_to_rgb(h: f32, s: f32, v: f32) -> Color {
|
||||
let h = h.rem_euclid(360.0);
|
||||
let c = v * s;
|
||||
let hh = h / 60.0;
|
||||
let x = c * (1.0 - (hh.rem_euclid(2.0) - 1.0).abs());
|
||||
let (r1, g1, b1) = match hh as u32 {
|
||||
0 => (c, x, 0.0),
|
||||
1 => (x, c, 0.0),
|
||||
2 => (0.0, c, x),
|
||||
3 => (0.0, x, c),
|
||||
4 => (x, 0.0, c),
|
||||
_ => (c, 0.0, x),
|
||||
};
|
||||
let m = v - c;
|
||||
Color {
|
||||
r: r1 + m,
|
||||
g: g1 + m,
|
||||
b: b1 + m,
|
||||
a: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// RGB (0..1) → HSV (h 0..360, s 0..1, v 0..1).
|
||||
pub fn rgb_to_hsv(c: Color) -> (f32, f32, f32) {
|
||||
let (r, g, b) = (c.r, c.g, c.b);
|
||||
let max = r.max(g).max(b);
|
||||
let min = r.min(g).min(b);
|
||||
let v = max;
|
||||
let delta = max - min;
|
||||
let s = if max <= 0.0 { 0.0 } else { delta / max };
|
||||
let h = if delta == 0.0 {
|
||||
0.0
|
||||
} else if max == r {
|
||||
60.0 * (((g - b) / delta) % 6.0)
|
||||
} else if max == g {
|
||||
60.0 * (((b - r) / delta) + 2.0)
|
||||
} else {
|
||||
60.0 * (((r - g) / delta) + 4.0)
|
||||
};
|
||||
let h = if h < 0.0 { h + 360.0 } else { h };
|
||||
(h, s, v)
|
||||
}
|
||||
|
||||
/// Match the host's `apply_cursor_move` drag dispatch with the
|
||||
/// document's stored kind — convenience the host calls when
|
||||
/// translating a press into the persistent drag state.
|
||||
/// Translate a press hit into the persistent drag kind the host keeps live.
|
||||
pub fn drag_for_hit(hit: ColorPickerHit) -> Option<ColorPickerDrag> {
|
||||
match hit {
|
||||
ColorPickerHit::SvBox => Some(ColorPickerDrag::SvBox),
|
||||
|
|
|
|||
2
vendor/jian
vendored
2
vendor/jian
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit f0fbf70d65a6161e47dccf19e208049c6597e307
|
||||
Subproject commit 9580247719eb4f89beaf6a1bbb1f67a9de9592a2
|
||||
Loading…
Reference in a new issue