Add an in-viewport color picker to the Gradient tool when double-clicking a color stop (#3834)
* Hide batched blocked debug print messages * Implement the color picker on double-clicking stops * Code review
This commit is contained in:
parent
82cf8eb369
commit
e62771845f
|
|
@ -359,10 +359,15 @@ impl Dispatcher {
|
|||
/// with a discriminant or the entire payload (depending on settings)
|
||||
fn log_message(&self, message: &Message, queues: &[VecDeque<Message>], message_logging_verbosity: MessageLoggingVerbosity) {
|
||||
let discriminant = MessageDiscriminant::from(message);
|
||||
let is_blocked = DEBUG_MESSAGE_BLOCK_LIST.contains(&discriminant) || DEBUG_MESSAGE_ENDING_BLOCK_LIST.iter().any(|blocked_name| discriminant.local_name().ends_with(blocked_name));
|
||||
let is_empty_batched = if let Message::Batched { messages } = message { messages.is_empty() } else { false };
|
||||
let is_blocked =
|
||||
|discriminant| DEBUG_MESSAGE_BLOCK_LIST.contains(&discriminant) || DEBUG_MESSAGE_ENDING_BLOCK_LIST.iter().any(|blocked_name| discriminant.local_name().ends_with(blocked_name));
|
||||
let is_batch_all_blocked = if let Message::Batched { messages } = message {
|
||||
messages.iter().all(|message| is_blocked(MessageDiscriminant::from(message)))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if !is_blocked && !is_empty_batched {
|
||||
if !is_blocked(discriminant) && !is_batch_all_blocked {
|
||||
match message_logging_verbosity {
|
||||
MessageLoggingVerbosity::Off => {}
|
||||
MessageLoggingVerbosity::Names => {
|
||||
|
|
|
|||
|
|
@ -151,6 +151,11 @@ pub enum FrontendMessage {
|
|||
#[serde(rename = "documentId")]
|
||||
document_id: DocumentId,
|
||||
},
|
||||
UpdateGradientStopColorPickerPosition {
|
||||
color: Color,
|
||||
x: f64,
|
||||
y: f64,
|
||||
},
|
||||
UpdateImportsExports {
|
||||
/// If the primary import is not visible, then it is None.
|
||||
imports: Vec<Option<FrontendGraphOutput>>,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
|||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_gradient};
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||
use graphene_std::raster::color::Color;
|
||||
use graphene_std::vector::style::{Fill, Gradient, GradientStops, GradientType};
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
|
|
@ -38,6 +39,10 @@ pub enum GradientToolMessage {
|
|||
PointerMove { constrain_axis: Key, lock_angle: Key },
|
||||
PointerOutsideViewport { constrain_axis: Key, lock_angle: Key },
|
||||
PointerUp,
|
||||
StartTransactionForColorStop,
|
||||
CommitTransactionForColorStop,
|
||||
CloseStopColorPicker,
|
||||
UpdateStopColor { color: Color },
|
||||
UpdateOptions { options: GradientOptionsUpdate },
|
||||
}
|
||||
|
||||
|
|
@ -63,18 +68,8 @@ impl ToolMetadata for GradientTool {
|
|||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for GradientTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
||||
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions { options }) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, false);
|
||||
|
||||
let has_gradient = has_gradient_on_selected_layers(context.document);
|
||||
if has_gradient != self.data.has_selected_gradient {
|
||||
self.data.has_selected_gradient = has_gradient;
|
||||
responses.add(ToolMessage::RefreshToolOptions);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
match options {
|
||||
match message {
|
||||
ToolMessage::Gradient(GradientToolMessage::UpdateOptions { options }) => match options {
|
||||
GradientOptionsUpdate::Type(gradient_type) => {
|
||||
self.options.gradient_type = gradient_type;
|
||||
apply_gradient_update(&mut self.data, context, responses, |g| g.gradient_type != gradient_type, |g| g.gradient_type = gradient_type);
|
||||
|
|
@ -87,6 +82,46 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
|||
GradientOptionsUpdate::ReverseDirection => {
|
||||
apply_gradient_update(&mut self.data, context, responses, |_| true, |g| std::mem::swap(&mut g.start, &mut g.end));
|
||||
}
|
||||
},
|
||||
ToolMessage::Gradient(GradientToolMessage::StartTransactionForColorStop) => {
|
||||
if self.data.color_picker_transaction_open {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
}
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
self.data.color_picker_transaction_open = true;
|
||||
}
|
||||
ToolMessage::Gradient(GradientToolMessage::CommitTransactionForColorStop) => {
|
||||
if self.data.color_picker_transaction_open {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
self.data.color_picker_transaction_open = false;
|
||||
}
|
||||
}
|
||||
ToolMessage::Gradient(GradientToolMessage::UpdateStopColor { color }) => {
|
||||
if let Some(stop_index) = self.data.color_picker_editing_color_stop
|
||||
&& let Some(selected_gradient) = &mut self.data.selected_gradient
|
||||
&& stop_index < selected_gradient.gradient.stops.color.len()
|
||||
{
|
||||
selected_gradient.gradient.stops.color[stop_index] = color;
|
||||
selected_gradient.render_gradient(responses);
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
}
|
||||
}
|
||||
ToolMessage::Gradient(GradientToolMessage::CloseStopColorPicker) => {
|
||||
if self.data.color_picker_transaction_open {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
self.data.color_picker_transaction_open = false;
|
||||
}
|
||||
self.data.color_picker_editing_color_stop = None;
|
||||
}
|
||||
_ => {
|
||||
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, false);
|
||||
|
||||
let has_gradient = has_gradient_on_selected_layers(context.document);
|
||||
if has_gradient != self.data.has_selected_gradient {
|
||||
self.data.has_selected_gradient = has_gradient;
|
||||
responses.add(ToolMessage::RefreshToolOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -515,6 +550,8 @@ struct GradientToolData {
|
|||
auto_pan_shift: DVec2,
|
||||
gradient_angle: f64,
|
||||
has_selected_gradient: bool,
|
||||
color_picker_editing_color_stop: Option<usize>,
|
||||
color_picker_transaction_open: bool,
|
||||
}
|
||||
|
||||
impl Fsm for GradientToolFsmState {
|
||||
|
|
@ -723,9 +760,31 @@ impl Fsm for GradientToolFsmState {
|
|||
let snap_data = SnapData::new(document, input, viewport);
|
||||
tool_data.snap_manager.draw_overlays(snap_data, &mut overlay_context);
|
||||
|
||||
// Update color picker position if active (keeps it anchored to the stop during pan/zoom)
|
||||
if let Some(stop_index) = tool_data.color_picker_editing_color_stop
|
||||
&& let Some(selected_gradient) = tool_data.selected_gradient.as_ref()
|
||||
&& let Some(layer) = selected_gradient.layer
|
||||
{
|
||||
let transform = gradient_space_transform(layer, document);
|
||||
let gradient = &selected_gradient.gradient;
|
||||
if stop_index < gradient.stops.position.len() {
|
||||
let color = gradient.stops.color[stop_index].to_gamma_srgb();
|
||||
let position = gradient.stops.position[stop_index];
|
||||
let DVec2 { x, y } = transform.transform_point2(gradient.start.lerp(gradient.end, position));
|
||||
responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color, x, y });
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(GradientToolFsmState::Ready { .. }, GradientToolMessage::SelectionChanged) => {
|
||||
if tool_data.color_picker_editing_color_stop.is_some() {
|
||||
if tool_data.color_picker_transaction_open {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
tool_data.color_picker_transaction_open = false;
|
||||
}
|
||||
tool_data.color_picker_editing_color_stop = None;
|
||||
}
|
||||
tool_data.selected_gradient = None;
|
||||
GradientToolFsmState::Ready {
|
||||
hovering: GradientHoverTarget::None,
|
||||
|
|
@ -737,12 +796,46 @@ impl Fsm for GradientToolFsmState {
|
|||
let drag_start_viewport = document.metadata().document_to_viewport.transform_point2(tool_data.drag_start);
|
||||
if input.mouse.position.distance(drag_start_viewport) <= DRAG_THRESHOLD
|
||||
&& let Some(selected_gradient) = &mut tool_data.selected_gradient
|
||||
&& let GradientDragTarget::Midpoint(index) = selected_gradient.dragging
|
||||
{
|
||||
match selected_gradient.dragging {
|
||||
GradientDragTarget::Midpoint(index) => {
|
||||
selected_gradient.gradient.stops.midpoint[index] = 0.5;
|
||||
selected_gradient.render_gradient(responses);
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
}
|
||||
GradientDragTarget::Start | GradientDragTarget::End | GradientDragTarget::Stop(_) => {
|
||||
// Find the stop index from the drag target
|
||||
let stop_index = match selected_gradient.dragging {
|
||||
GradientDragTarget::Stop(i) => Some(i),
|
||||
GradientDragTarget::Start => selected_gradient.gradient.stops.position.iter().position(|&p| p.abs() < f64::EPSILON * 1000.),
|
||||
GradientDragTarget::End => selected_gradient.gradient.stops.position.iter().position(|&p| (1. - p).abs() < f64::EPSILON * 1000.),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(stop_index) = stop_index
|
||||
&& stop_index < selected_gradient.gradient.stops.color.len()
|
||||
{
|
||||
// Dismiss any existing color picker first
|
||||
if tool_data.color_picker_editing_color_stop.is_some() && tool_data.color_picker_transaction_open {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
tool_data.color_picker_transaction_open = false;
|
||||
}
|
||||
|
||||
let stop_pos = selected_gradient.gradient.stops.position[stop_index];
|
||||
let viewport_pos = selected_gradient
|
||||
.transform
|
||||
.transform_point2(selected_gradient.gradient.start.lerp(selected_gradient.gradient.end, stop_pos));
|
||||
let color = selected_gradient.gradient.stops.color[stop_index].to_gamma_srgb();
|
||||
tool_data.color_picker_editing_color_stop = Some(stop_index);
|
||||
responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition {
|
||||
color,
|
||||
x: viewport_pos.x,
|
||||
y: viewport_pos.y,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
(state, GradientToolMessage::DeleteStop) => {
|
||||
|
|
@ -1178,15 +1271,21 @@ impl Fsm for GradientToolFsmState {
|
|||
tool_data.selected_gradient = None;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
dismiss_color_stop_color_picker(tool_data, responses);
|
||||
|
||||
GradientToolFsmState::Ready {
|
||||
hovering: GradientHoverTarget::None,
|
||||
selected: GradientSelectedTarget::None,
|
||||
}
|
||||
}
|
||||
(_, GradientToolMessage::Abort) => GradientToolFsmState::Ready {
|
||||
(_, GradientToolMessage::Abort) => {
|
||||
dismiss_color_stop_color_picker(tool_data, responses);
|
||||
|
||||
GradientToolFsmState::Ready {
|
||||
hovering: GradientHoverTarget::None,
|
||||
selected: GradientSelectedTarget::None,
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
|
@ -1273,6 +1372,16 @@ impl Fsm for GradientToolFsmState {
|
|||
}
|
||||
}
|
||||
|
||||
fn dismiss_color_stop_color_picker(tool_data: &mut GradientToolData, responses: &mut VecDeque<Message>) {
|
||||
if tool_data.color_picker_editing_color_stop.is_some() {
|
||||
if tool_data.color_picker_transaction_open {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
tool_data.color_picker_transaction_open = false;
|
||||
}
|
||||
tool_data.color_picker_editing_color_stop = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_hover_target(mouse: DVec2, document: &DocumentMessageHandler) -> GradientHoverTarget {
|
||||
let stop_tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);
|
||||
let midpoint_tolerance = GRADIENT_MIDPOINT_DIAMOND_RADIUS.powi(2);
|
||||
|
|
@ -1380,7 +1489,7 @@ fn apply_gradient_update(
|
|||
}
|
||||
|
||||
if transaction_started {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
}
|
||||
if let Some(selected_gradient) = &mut data.selected_gradient
|
||||
&& let Some(layer) = selected_gradient.layer
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { getContext, onDestroy, createEventDispatcher } from "svelte";
|
||||
import { getContext, onDestroy, createEventDispatcher, tick } from "svelte";
|
||||
|
||||
import type { HSV, RGB, FillChoice, MenuDirection } from "@graphite/messages";
|
||||
import { Color, contrastingOutlineFactor, Gradient } from "@graphite/messages";
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
["Magenta", "#ff00ff", "#696969"],
|
||||
];
|
||||
|
||||
const dispatch = createEventDispatcher<{ colorOrGradient: FillChoice; startHistoryTransaction: undefined }>();
|
||||
const dispatch = createEventDispatcher<{ colorOrGradient: FillChoice; startHistoryTransaction: undefined; commitHistoryTransaction: undefined }>();
|
||||
const tooltip = getContext<TooltipState>("tooltip");
|
||||
|
||||
export let colorOrGradient: FillChoice;
|
||||
|
|
@ -109,10 +109,11 @@
|
|||
return new Color({ h, s, v, a });
|
||||
}
|
||||
|
||||
function watchOpen(open: boolean) {
|
||||
async function watchOpen(open: boolean) {
|
||||
if (open) {
|
||||
setTimeout(() => hexCodeInputWidget?.focus(), 0);
|
||||
} else {
|
||||
|
||||
await tick();
|
||||
setOldHSVA(hue, saturation, value, alpha, isNone);
|
||||
}
|
||||
}
|
||||
|
|
@ -198,6 +199,7 @@
|
|||
}
|
||||
|
||||
function onPointerUp() {
|
||||
if (draggingPickerTrack) dispatch("commitHistoryTransaction");
|
||||
removeEvents();
|
||||
}
|
||||
|
||||
|
|
@ -413,6 +415,10 @@
|
|||
setOldHSVA(hsva.h, hsva.s, hsva.v, hsva.a, color.none);
|
||||
}
|
||||
|
||||
export function div(): HTMLDivElement | undefined {
|
||||
return self?.div();
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
removeEvents();
|
||||
});
|
||||
|
|
@ -705,6 +711,7 @@
|
|||
|
||||
<style lang="scss" global>
|
||||
.color-picker {
|
||||
--widget-height: 24px;
|
||||
--picker-size: 256px;
|
||||
--picker-circle-radius: 6px;
|
||||
|
||||
|
|
|
|||
|
|
@ -199,7 +199,8 @@
|
|||
}
|
||||
|
||||
const inParentFloatingMenu = Boolean(floatingMenuContainer.closest("[data-floating-menu-content]"));
|
||||
if (!inParentFloatingMenu) {
|
||||
const noPosition = Boolean(floatingMenuContainer.closest("[data-floating-menu-no-position]"));
|
||||
if (!inParentFloatingMenu && !noPosition) {
|
||||
// Required to correctly position content when scrolled (it has a `position: fixed` to prevent clipping)
|
||||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||
let tailOffset = 0;
|
||||
|
|
@ -322,7 +323,7 @@
|
|||
|
||||
// POINTER STRAY
|
||||
// Close the floating menu if the pointer has strayed far enough from its bounds (and it's not hovering over its own spawner)
|
||||
const notHoveringOverOwnSpawner = ownSpawner !== targetSpawner;
|
||||
const notHoveringOverOwnSpawner = ownSpawner !== targetSpawner || (ownSpawner === undefined && targetSpawner === undefined);
|
||||
if (strayCloses && notHoveringOverOwnSpawner && isPointerEventOutsideFloatingMenu(e, POINTER_STRAY_DISTANCE)) {
|
||||
// TODO: Extend this rectangle bounds check to all submenu bounds up the DOM tree since currently submenus disappear
|
||||
// TODO: with zero stray distance if the cursor is further than the stray distance from only the top-level menu
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import {
|
||||
type MenuDirection,
|
||||
type MouseCursorIcon,
|
||||
type XY,
|
||||
Color,
|
||||
DisplayEditableTextbox,
|
||||
DisplayEditableTextboxUpdateFontData,
|
||||
DisplayEditableTextboxTransform,
|
||||
|
|
@ -14,6 +16,7 @@
|
|||
UpdateDocumentRulers,
|
||||
UpdateDocumentScrollbars,
|
||||
UpdateEyedropperSamplingState,
|
||||
UpdateGradientStopColorPickerPosition,
|
||||
UpdateMouseCursor,
|
||||
isWidgetSpanRow,
|
||||
} from "@graphite/messages";
|
||||
|
|
@ -24,6 +27,7 @@
|
|||
import { rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization";
|
||||
import { setupViewportResizeObserver, cleanupViewportResizeObserver } from "@graphite/utility-functions/viewports";
|
||||
|
||||
import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte";
|
||||
import EyedropperPreview, { ZOOM_WINDOW_DIMENSIONS } from "@graphite/components/floating-menus/EyedropperPreview.svelte";
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
|
|
@ -35,6 +39,7 @@
|
|||
let rulerHorizontal: RulerInput | undefined;
|
||||
let rulerVertical: RulerInput | undefined;
|
||||
let viewport: HTMLDivElement | undefined;
|
||||
let gradientStopPicker: ColorPicker | undefined;
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
const appWindow = getContext<AppWindowState>("appWindow");
|
||||
|
|
@ -75,6 +80,10 @@
|
|||
let cursorEyedropperPreviewColorPrimary = "";
|
||||
let cursorEyedropperPreviewColorSecondary = "";
|
||||
|
||||
// Gradient stop color picker
|
||||
let gradientStopPickerColor: Color | undefined = undefined;
|
||||
let gradientStopPickerPosition: { x: number; y: number } | undefined = undefined;
|
||||
|
||||
// Canvas dimensions
|
||||
let canvasWidth: number | undefined = undefined;
|
||||
let canvasHeight: number | undefined = undefined;
|
||||
|
|
@ -406,6 +415,19 @@
|
|||
// which provides pixel-perfect physical dimensions via devicePixelContentBoxSize
|
||||
}
|
||||
|
||||
function gradientStopPickerDirection(position: XY | undefined, viewport: HTMLDivElement | undefined): MenuDirection {
|
||||
const picker = (gradientStopPicker?.div()?.querySelector("[data-floating-menu-content]") || undefined) as HTMLElement | undefined;
|
||||
if (!picker || !position || !viewport) return "Bottom";
|
||||
|
||||
const roomRight = position.x + picker.offsetWidth - viewport.clientWidth;
|
||||
const roomBelow = position.y + picker.offsetHeight - viewport.clientHeight;
|
||||
|
||||
// Prefer bottom if there's room
|
||||
if (roomBelow <= 0) return "Bottom";
|
||||
// Otherwise choose the direction with more room
|
||||
return roomRight > roomBelow ? "Bottom" : "Right";
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Not compatible with Safari:
|
||||
// <https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#browser_compatibility>
|
||||
|
|
@ -441,6 +463,12 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Gradient stop color picker
|
||||
editor.subscriptions.subscribeJsMessage(UpdateGradientStopColorPickerPosition, (data) => {
|
||||
gradientStopPickerColor = data.color;
|
||||
gradientStopPickerPosition = { x: data.x, y: data.y };
|
||||
});
|
||||
|
||||
// Update scrollbars and rulers
|
||||
editor.subscriptions.subscribeJsMessage(UpdateDocumentScrollbars, async (data) => {
|
||||
await tick();
|
||||
|
|
@ -564,6 +592,34 @@
|
|||
y={cursorTop}
|
||||
/>
|
||||
{/if}
|
||||
<div
|
||||
style:left={gradientStopPickerPosition ? `${gradientStopPickerPosition?.x}px` : undefined}
|
||||
style:top={gradientStopPickerPosition ? `${gradientStopPickerPosition?.y}px` : undefined}
|
||||
style:position="absolute"
|
||||
data-floating-menu-no-position
|
||||
>
|
||||
<div data-floating-menu-spawner></div>
|
||||
<ColorPicker
|
||||
direction={gradientStopPickerDirection(gradientStopPickerPosition, viewport)}
|
||||
open={Boolean(gradientStopPickerPosition && gradientStopPickerColor)}
|
||||
on:open={({ detail }) => {
|
||||
if (!detail) {
|
||||
editor.handle.closeGradientStopColorPicker();
|
||||
gradientStopPickerPosition = undefined;
|
||||
gradientStopPickerColor = undefined;
|
||||
}
|
||||
}}
|
||||
colorOrGradient={gradientStopPickerColor || new Color()}
|
||||
on:colorOrGradient={({ detail }) => {
|
||||
if (detail instanceof Color) {
|
||||
editor.handle.updateGradientStopColor(detail.red, detail.green, detail.blue, detail.alpha);
|
||||
}
|
||||
}}
|
||||
on:startHistoryTransaction={() => editor.handle.startGradientStopColorTransaction()}
|
||||
on:commitHistoryTransaction={() => editor.handle.commitGradientStopColorTransaction()}
|
||||
bind:this={gradientStopPicker}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class:viewport={!$appWindow.viewportHolePunch}
|
||||
class:viewport-transparent={$appWindow.viewportHolePunch}
|
||||
|
|
|
|||
|
|
@ -32,11 +32,7 @@
|
|||
</script>
|
||||
|
||||
<LayoutCol class="color-button" classes={{ open, disabled, narrow, none, transparency, outlined, "direction-top": menuDirection === "Top" }} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
|
||||
<button style:--chosen-gradient={chosenGradient} style:--outline-amount={outlineFactor} on:click={() => (open = true)} tabindex="0" data-floating-menu-spawner>
|
||||
<!-- {#if disabled && value instanceof Color && !value.none}
|
||||
<TextLabel>sRGB</TextLabel>
|
||||
{/if} -->
|
||||
</button>
|
||||
<button style:--chosen-gradient={chosenGradient} style:--outline-amount={outlineFactor} on:click={() => (open = true)} tabindex="0" data-floating-menu-spawner></button>
|
||||
<ColorPicker
|
||||
{open}
|
||||
{disabled}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,8 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
potentiallyRestoreCanvasFocus(e);
|
||||
|
||||
const { target } = e;
|
||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-viewport-container], [data-node-graph]");
|
||||
const inFloatingMenu = target instanceof Element && target.closest("[data-floating-menu-content]");
|
||||
const isTargetingCanvas = !inFloatingMenu && target instanceof Element && target.closest("[data-viewport], [data-viewport-container], [data-node-graph]");
|
||||
const inDialog = target instanceof Element && target.closest("[data-dialog] [data-floating-menu-content]");
|
||||
const inContextMenu = target instanceof Element && target.closest("[data-context-menu]");
|
||||
const inTextInput = target === textToolInteractiveInputElement;
|
||||
|
|
|
|||
|
|
@ -828,6 +828,15 @@ export class DisplayEditableTextboxTransform extends JsMessage {
|
|||
|
||||
export class DisplayRemoveEditableTextbox extends JsMessage {}
|
||||
|
||||
export class UpdateGradientStopColorPickerPosition extends JsMessage {
|
||||
@Type(() => Color)
|
||||
readonly color!: Color;
|
||||
|
||||
readonly x!: number;
|
||||
|
||||
readonly y!: number;
|
||||
}
|
||||
|
||||
export class UpdateDocumentLayerDetails extends JsMessage {
|
||||
@Type(() => LayerPanelEntry)
|
||||
readonly data!: LayerPanelEntry;
|
||||
|
|
@ -1713,6 +1722,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||
UpdateEyedropperSamplingState,
|
||||
UpdateFullscreen,
|
||||
UpdateGraphFadeArtwork,
|
||||
UpdateGradientStopColorPickerPosition,
|
||||
UpdateGraphViewOverlay,
|
||||
UpdateImportReorderIndex,
|
||||
UpdateImportsExports,
|
||||
|
|
|
|||
|
|
@ -650,6 +650,34 @@ impl EditorHandle {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Update the color of the currently-edited gradient stop
|
||||
#[wasm_bindgen(js_name = updateGradientStopColor)]
|
||||
pub fn update_gradient_stop_color(&self, red: f32, green: f32, blue: f32, alpha: f32) -> Result<(), JsValue> {
|
||||
let Some(color) = Color::from_rgbaf32(red, green, blue, alpha) else {
|
||||
return Err(Error::new("Invalid color").into());
|
||||
};
|
||||
self.dispatch(GradientToolMessage::UpdateStopColor { color: color.to_linear_srgb() });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start a new undo transaction for gradient stop color editing
|
||||
#[wasm_bindgen(js_name = startGradientStopColorTransaction)]
|
||||
pub fn start_gradient_stop_color_transaction(&self) {
|
||||
self.dispatch(GradientToolMessage::StartTransactionForColorStop);
|
||||
}
|
||||
|
||||
/// Commit the current gradient stop color transaction (called on pointer-up after each drag/click)
|
||||
#[wasm_bindgen(js_name = commitGradientStopColorTransaction)]
|
||||
pub fn commit_gradient_stop_color_transaction(&self) {
|
||||
self.dispatch(GradientToolMessage::CommitTransactionForColorStop);
|
||||
}
|
||||
|
||||
/// Close the gradient stop color picker and commit any pending transaction
|
||||
#[wasm_bindgen(js_name = closeGradientStopColorPicker)]
|
||||
pub fn close_gradient_stop_color_picker(&self) {
|
||||
self.dispatch(GradientToolMessage::CloseStopColorPicker);
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = clipLayer)]
|
||||
pub fn clip_layer(&self, id: u64) {
|
||||
let id = NodeId(id);
|
||||
|
|
|
|||
Loading…
Reference in New Issue