mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-15 11:51:33 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dd66b8f20 | ||
|
|
d5165c3e75 |
@@ -1,131 +0,0 @@
|
||||
# Color picker
|
||||
|
||||
The color picker is a local HTML dialog hosted by `ColorPickerDialog`, a
|
||||
`WebViewHostDialog` subclass. Its header and implementation own the ordered
|
||||
selection contract and shared filament conversion. Bridge validation, favorites
|
||||
persistence, placement and gettext strings remain private to the implementation.
|
||||
Its colors use the existing `ColorRGBA` utility.
|
||||
It uses a native title bar and fixed window border, allowing dragging but no
|
||||
manual resizing or maximization. Native title-bar colors use the application's
|
||||
shared dialog dark-theme update on creation and theme changes.
|
||||
Like SpeedDial, the host disables browser zoom accelerators, restores native
|
||||
WebView focus on activation and readiness, and synchronizes layout and the macOS
|
||||
viewport before repainting the backend. Activation never dismisses this modal
|
||||
dialog; focus restoration preserves an active editor and its partial input.
|
||||
The initialized page reports
|
||||
its intrinsic content height; native code validates the current page identifier
|
||||
and bounded numeric height, converts CSS pixels to device-independent window
|
||||
size, and fits within the display's work area. Identical sizes are ignored. The
|
||||
page observes content rather than viewport height, so resizing does not create
|
||||
a feedback loop; oversized content remains scrollable.
|
||||
The panel snapshots the triggering color or More Colors button's screen rectangle.
|
||||
It aligns below the button, flips above when needed, and clamps to that button's
|
||||
display work area on initial display. Later content and DPI changes update only
|
||||
the client size, preserving the position chosen by dragging the title bar.
|
||||
wxGTK clears the dialog's default
|
||||
parent-centering constraint and reapplies its native position after showing,
|
||||
bypassing wxGTK's requested-position cache. Absolute top-level placement remains
|
||||
subject to the window manager; Wayland compositors control it. The official filament palette's
|
||||
existing placement is independent of this custom panel.
|
||||
A solid selection contains one color; a gradient contains exactly two ordered
|
||||
endpoints. The bridge represents these as `{"type":"solid|gradient",
|
||||
"colors":["#RRGGBBAA", ...]}`. RGB input is also accepted and becomes opaque.
|
||||
Native validation rejects malformed hex and incorrect cardinality.
|
||||
|
||||
The project filament color action and the official filament picker's More Colors
|
||||
action enable two-endpoint gradients and independent endpoint alpha. The preset
|
||||
combo box reads project colors in their stored vector order locally and applies
|
||||
changes through `sync_colour_config`,
|
||||
retaining the existing RGB/RGBA profile/project formats and dirty marking.
|
||||
Sidebar gradient and segmented multicolor swatches draw colors in the supplied
|
||||
order without sorting them.
|
||||
Opaque colors use `#RRGGBB`; nonopaque colors use `#RRGGBBAA`, including fully
|
||||
transparent alpha `00`. Initial selections, alpha-only edits and cancellation
|
||||
retain stored alpha. Ordered
|
||||
custom results bypass the official palette's `FilamentColor` set. Choosing an
|
||||
official swatch retains its existing palette behavior.
|
||||
The Linux sidebar's Change extruder color menu uses this same custom editor and
|
||||
the filament swatch anchor, including alpha, ordered gradients and configuration syncing.
|
||||
|
||||
Striped colors and gradients with more than two endpoints cannot be represented
|
||||
by this editor. A notice explains that the current selection is retained; the
|
||||
editor starts from the first color, and unchanged confirmation is a no-op.
|
||||
Cancellation is also a no-op. If the WebView backend cannot initialize, a local
|
||||
unavailable panel offers Cancel and Escape, without opening a system color picker.
|
||||
|
||||
Generic color option fields use an ordinary color button on every platform and
|
||||
open the same panel with gradients and alpha disabled. The panel is anchored to
|
||||
the field button. Empty or invalid field values remain undefined (the empty
|
||||
string), distinct from opaque black. Right-click resets to undefined. Cancel does
|
||||
not notify the field; confirmation and reset notify only when its RGB string
|
||||
changes. Programmatic assignments remain silent, and reading a field never
|
||||
writes legacy favorites.
|
||||
|
||||
AMS material settings retain their fixed/AMS swatches and first-level popup. Only
|
||||
its custom-color action opens the solid, opaque editor, anchored to that custom
|
||||
control. The transient popup releases its grab while the modal is open and returns
|
||||
to the same selection on cancellation. Confirmation uses the existing default-color
|
||||
and packed RGBA event path. Device permission/status checks remain at the first-level
|
||||
entry.
|
||||
|
||||
Texture import's color-mapping Add Material action opens a solid, opaque editor
|
||||
anchored to the mapping row. The existing filament limit is checked before opening.
|
||||
The first-level material popup closes as an action; its callback is copied before
|
||||
dismissal, so popup destruction does not invalidate the modal's completion path.
|
||||
Confirmation invokes the existing virtual-filament callback once; cancellation
|
||||
adds no material.
|
||||
|
||||
Native gettext strings are installed at document start. Static page labels and
|
||||
palette names use stable English keys; RAL codes, channel symbols, and numeric
|
||||
labels remain data. The palette color values do not depend on the UI language.
|
||||
The application locale also sets the document language. Numeric editor validation
|
||||
uses the application's gettext messages, rather than browser language defaults;
|
||||
missing translations fall back to the stable English keys.
|
||||
|
||||
The pure color/state regressions run with
|
||||
`node --test resources/web/dialog/ColorPickerDialog/colorpicker.test.js`.
|
||||
Optional real-browser regressions live beside them in
|
||||
`colorpicker.browser.test.js`. They use an existing Playwright installation
|
||||
(`ORCA_PLAYWRIGHT_MODULE` can name its module path) and an installed browser
|
||||
(`ORCA_BROWSER_EXECUTABLE` can name its executable), without adding a production
|
||||
dependency. They cover bridge initialization, capability combinations, invalid
|
||||
editor recovery, ordered endpoint alpha, favorites, gettext keys, and live theme
|
||||
changes. These browser checks complement native contract/persistence tests; they
|
||||
do not establish the full native modal confirmation and favorites journey.
|
||||
|
||||
Gradient and alpha capabilities are independent and default off. Unsupported
|
||||
gradients are rejected; disabled alpha normalizes the selected result to opaque.
|
||||
Favorites retain both capabilities independently of the current caller. The page
|
||||
stores alpha as a byte and only converts to percentage when displaying or editing
|
||||
opacity, so switching endpoints does not change alpha precision.
|
||||
|
||||
The page starts disabled. Each document supplies a page identifier in its ready
|
||||
message, receives native initialization, and acknowledges it before native code
|
||||
accepts confirmation or favorite updates. Repeated initialization does not reset
|
||||
edits, and messages from an obsolete document are ignored. Native WebView message
|
||||
handling is deferred and guarded by a lifetime token. Only a validated confirmation
|
||||
sets the optional result. Cancel, Escape and native window close leave it empty.
|
||||
|
||||
Favorites belong to application configuration, separately from the selected result,
|
||||
in the editor configuration returned by `AppConfig::config_path()` under `data_dir()`.
|
||||
The `color_picker` section has string `version` equal to `1` and a `favorites`
|
||||
string containing a JSON array of typed selections. Native helpers normalize and
|
||||
deduplicate this array in order and limit it to 24 entries. Updates replace the
|
||||
entire section and immediately call `AppConfig::save()`, so accepted favorite
|
||||
changes survive cancellation. Empty arrays are saved explicitly. Core `AppConfig`
|
||||
only stores the raw section; color validation belongs to the GUI boundary.
|
||||
|
||||
When the section is absent, valid solid colors from `custom_color_list` are imported
|
||||
once, without changing that legacy section. The legacy native picker saves decimal
|
||||
`r,g,b,a` byte strings; hexadecimal RGB and RGBA strings are also supported. Migration
|
||||
strictly validates all four decimal channels and preserves alpha. Gradient strings
|
||||
and malformed values are ignored. The new version and an empty array are
|
||||
saved even when there is nothing to import. A present section with an unknown or
|
||||
missing version is left untouched and disables favorite writes. Malformed version
|
||||
1 data is displayed as empty and remains on disk until the user explicitly saves
|
||||
a valid replacement. Other configuration sections and project/profile color
|
||||
formats are independent of this storage.
|
||||
|
||||
Interface colors consume the host's `data-orca-theme` and `--orca-*` contract through
|
||||
shared styles. The picker opts into scoped global control roles. Application theme
|
||||
changes update UI surfaces without reloading the page or changing color data.
|
||||
+56
-1336
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -325,7 +325,6 @@ src/slic3r/GUI/BaseTransparentDPIFrame.cpp
|
||||
src/slic3r/GUI/CloneDialog.cpp
|
||||
src/slic3r/GUI/ColorDecomposeDialog.cpp
|
||||
src/slic3r/GUI/ColorDecomposeSupport.cpp
|
||||
src/slic3r/GUI/ColorPickerDialog.cpp
|
||||
src/slic3r/GUI/ConfigWizard.cpp
|
||||
src/slic3r/GUI/DeviceCore/DevCalib.cpp
|
||||
src/slic3r/GUI/DeviceCore/DevUpgrade.cpp
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1909
-4922
File diff suppressed because it is too large
Load Diff
+2709
-6162
File diff suppressed because it is too large
Load Diff
@@ -592,10 +592,6 @@
|
||||
"name": "SUNLU PETG @base",
|
||||
"sub_path": "filament/SUNLU/SUNLU PETG @base.json"
|
||||
},
|
||||
{
|
||||
"name": "School House Spools PETG @base",
|
||||
"sub_path": "filament/SchoolHouseSpools/School House Spools PETG @base.json"
|
||||
},
|
||||
{
|
||||
"name": "eSUN PETG @base",
|
||||
"sub_path": "filament/eSUN/eSUN PETG @base.json"
|
||||
@@ -1100,10 +1096,6 @@
|
||||
"name": "SUNLU Wood PLA @base",
|
||||
"sub_path": "filament/SUNLU/SUNLU Wood PLA @base.json"
|
||||
},
|
||||
{
|
||||
"name": "School House Spools PLA @base",
|
||||
"sub_path": "filament/SchoolHouseSpools/School House Spools PLA @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Valment PLA @base",
|
||||
"sub_path": "filament/Valment/Valment PLA @base.json"
|
||||
@@ -2268,10 +2260,6 @@
|
||||
"name": "SUNLU PETG @System",
|
||||
"sub_path": "filament/SUNLU/SUNLU PETG @System.json"
|
||||
},
|
||||
{
|
||||
"name": "School House Spools PETG @System",
|
||||
"sub_path": "filament/SchoolHouseSpools/School House Spools PETG @System.json"
|
||||
},
|
||||
{
|
||||
"name": "eSUN PETG @System",
|
||||
"sub_path": "filament/eSUN/eSUN PETG @System.json"
|
||||
@@ -3824,10 +3812,6 @@
|
||||
"name": "SUNLU Wood PLA @System",
|
||||
"sub_path": "filament/SUNLU/SUNLU Wood PLA @System.json"
|
||||
},
|
||||
{
|
||||
"name": "School House Spools PLA @System",
|
||||
"sub_path": "filament/SchoolHouseSpools/School House Spools PLA @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Valment PLA @System",
|
||||
"sub_path": "filament/Valment/Valment PLA @System.json"
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "School House Spools PETG @System",
|
||||
"inherits": "School House Spools PETG @base",
|
||||
"from": "system",
|
||||
"setting_id": "RSu2orfhR8ktT36h",
|
||||
"instantiation": "true",
|
||||
"filament_max_volumetric_speed": [
|
||||
"13"
|
||||
],
|
||||
"filament_long_retractions_when_cut": [
|
||||
"1"
|
||||
],
|
||||
"filament_retraction_distances_when_cut": [
|
||||
"18"
|
||||
],
|
||||
"compatible_printers": []
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "School House Spools PETG @base",
|
||||
"inherits": "fdm_filament_pet",
|
||||
"from": "system",
|
||||
"filament_id": "OFnaCGQ9",
|
||||
"instantiation": "false",
|
||||
"cool_plate_temp": [
|
||||
"0"
|
||||
],
|
||||
"cool_plate_temp_initial_layer": [
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
"70"
|
||||
],
|
||||
"eng_plate_temp_initial_layer": [
|
||||
"70"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"30"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"40"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_cost": [
|
||||
"16.00"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.25"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.95"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"8"
|
||||
],
|
||||
"filament_multitool_ramming_flow": [
|
||||
"8"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"School House Spools"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"70"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"70"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"252"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"252"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"270"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"230"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"90"
|
||||
],
|
||||
"overhang_fan_threshold": [
|
||||
"10%"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"12"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"70"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"70"
|
||||
]
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "School House Spools PLA @System",
|
||||
"inherits": "School House Spools PLA @base",
|
||||
"from": "system",
|
||||
"setting_id": "3rHJk6SVAvFzyHI0",
|
||||
"instantiation": "true",
|
||||
"filament_max_volumetric_speed": [
|
||||
"21"
|
||||
],
|
||||
"filament_long_retractions_when_cut": [
|
||||
"1"
|
||||
],
|
||||
"filament_retraction_distances_when_cut": [
|
||||
"18"
|
||||
],
|
||||
"compatible_printers": []
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "School House Spools PLA @base",
|
||||
"inherits": "fdm_filament_pla",
|
||||
"from": "system",
|
||||
"filament_id": "OFHzwmT9",
|
||||
"instantiation": "false",
|
||||
"filament_cost": [
|
||||
"17.00"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.26"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.98"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"School House Spools"
|
||||
],
|
||||
"filament_scarf_seam_type": [
|
||||
"none"
|
||||
],
|
||||
"filament_scarf_height": [
|
||||
"10%"
|
||||
],
|
||||
"filament_scarf_gap": [
|
||||
"0%"
|
||||
],
|
||||
"filament_scarf_length": [
|
||||
"10"
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "RatRig",
|
||||
"version": "02.04.00.07",
|
||||
"version": "02.04.00.06",
|
||||
"force_update": "0",
|
||||
"description": "RatRig configurations",
|
||||
"machine_model_list": [
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
"bed_exclude_area": [
|
||||
"0x0"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single]",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single]",
|
||||
"machine_end_gcode": "END_PRINT",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
/* Pure color/state helpers shared by the page and its node:test contract tests. */
|
||||
(function (root, factory) {
|
||||
"use strict";
|
||||
const api = factory();
|
||||
if (typeof module === "object" && module.exports) module.exports = api;
|
||||
else root.ColorPickerModel = api;
|
||||
})(typeof globalThis === "object" ? globalThis : this, function () {
|
||||
"use strict";
|
||||
|
||||
function parseHex(hex) {
|
||||
if (typeof hex !== "string" || !/^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(hex)) return null;
|
||||
return [1, 3, 5, 7].map((offset) => offset === 7 && hex.length === 7 ? 255 : parseInt(hex.slice(offset, offset + 2), 16));
|
||||
}
|
||||
|
||||
function toHex(color) {
|
||||
return "#" + color.map((byte) => Math.round(byte).toString(16).padStart(2, "0")).join("").toUpperCase();
|
||||
}
|
||||
|
||||
const MAX_GRADIENT_COLORS = 7; // Most colors a gradient may have. A caller allows 2 (the default) up to this many.
|
||||
const NEW_GRADIENT_HUE_SHIFT = 75; // Hue step used for every newly created gradient color.
|
||||
const gradientLimit = (value) => Math.max(2, Math.min(MAX_GRADIENT_COLORS, Math.floor(Number(value)) || 2));
|
||||
|
||||
// `maxGradientColors` caps how many colors a gradient may have (2 when omitted).
|
||||
function normalizeSelection(value, options = {}, maxGradientColors = 2) {
|
||||
if (!value || !["solid", "gradient"].includes(value.type) || !Array.isArray(value.colors)) return null;
|
||||
if (value.type === "gradient" && !options.allow_gradient) return null;
|
||||
const count = value.colors.length;
|
||||
if (value.type === "solid" ? count !== 1 : count < 2 || count > gradientLimit(maxGradientColors)) return null;
|
||||
const colors = value.colors.map(parseHex);
|
||||
if (colors.some((color) => color === null)) return null;
|
||||
if (!options.allow_alpha) colors.forEach((color) => { color[3] = 255; });
|
||||
return {type: value.type, colors: colors.map(toHex)};
|
||||
}
|
||||
|
||||
function rgbToHsl(r, g, b) {
|
||||
r /= 255; g /= 255; b /= 255;
|
||||
const max = Math.max(r, g, b), min = Math.min(r, g, b), delta = max - min;
|
||||
let h = 0, s = 0;
|
||||
const l = (max + min) / 2;
|
||||
if (delta) {
|
||||
s = delta / (1 - Math.abs(2 * l - 1));
|
||||
if (max === r) h = ((g - b) / delta + (g < b ? 6 : 0)) / 6;
|
||||
else if (max === g) h = ((b - r) / delta + 2) / 6;
|
||||
else h = ((r - g) / delta + 4) / 6;
|
||||
}
|
||||
return [h * 360, s * 100, l * 100];
|
||||
}
|
||||
|
||||
function hslToRgb(h, s, l) {
|
||||
h = ((h % 360) + 360) % 360 / 60; s /= 100; l /= 100;
|
||||
const c = (1 - Math.abs(2 * l - 1)) * s;
|
||||
const x = c * (1 - Math.abs(h % 2 - 1)), m = l - c / 2;
|
||||
const sectors = [[c,x,0], [x,c,0], [0,c,x], [0,x,c], [x,0,c], [c,0,x]];
|
||||
return sectors[Math.floor(h)].map((channel) => Math.round((channel + m) * 255));
|
||||
}
|
||||
|
||||
function contrastColor(color, hueShift = 90) {
|
||||
if (!Number.isFinite(hueShift)) hueShift = 90;
|
||||
const degrees = Math.max(-180, Math.min(180, hueShift));
|
||||
const amount = Math.abs(degrees) / 180;
|
||||
const [h, s, l] = rgbToHsl(color[0], color[1], color[2]);
|
||||
const grey = s < 10;
|
||||
const target = grey ? (l < 50 ? 100 : 0) : 100 - l;
|
||||
const rgb = hslToRgb(h + degrees, grey ? 0 : s, l + (target - l) * amount);
|
||||
return [...rgb, color[3]];
|
||||
}
|
||||
|
||||
// options.max_gradient_colors (2..7, default 2) is how many colors this page lets a gradient have.
|
||||
function createState(options = {}, selection = {type: "solid", colors: ["#808080"]}) {
|
||||
const capabilities = {allow_gradient: options.allow_gradient === true, allow_alpha: options.allow_alpha === true};
|
||||
// gradient stays null until a selection provides one or the user switches to gradient mode.
|
||||
const state = {options: capabilities, maxGradientColors: gradientLimit(options.max_gradient_colors),
|
||||
mode: "solid", active: 0, solid: [128,128,128,255], gradient: null};
|
||||
if (!applySelection(state, selection)) return null;
|
||||
return state;
|
||||
}
|
||||
|
||||
function applySelection(state, value) {
|
||||
const selection = normalizeSelection(value, state.options, state.maxGradientColors);
|
||||
if (!selection) return false;
|
||||
state.mode = selection.type;
|
||||
const colors = selection.colors.map(parseHex);
|
||||
if (state.mode === "solid") state.solid = colors[0];
|
||||
else {
|
||||
state.gradient = colors;
|
||||
state.active = Math.min(state.active, colors.length - 1); // A gradient with fewer colors must not leave the selected stop past its end.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function activeColor(state) { return state.mode === "solid" ? state.solid : state.gradient[state.active]; }
|
||||
|
||||
function setMode(state, mode) {
|
||||
if (mode !== "solid" && (mode !== "gradient" || !state.options.allow_gradient)) return false;
|
||||
// A new gradient starts at the current solid color and ends at its contrast color.
|
||||
if (mode === "gradient" && !state.gradient) state.gradient = [[...state.solid], contrastColor(state.solid, NEW_GRADIENT_HUE_SHIFT)];
|
||||
state.mode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
function addGradientColor(state) {
|
||||
if (state.mode !== "gradient" || state.gradient.length >= state.maxGradientColors) return false;
|
||||
state.gradient.splice(state.active + 1, 0, contrastColor(state.gradient[state.active], NEW_GRADIENT_HUE_SHIFT));
|
||||
state.active += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
function removeGradientColor(state) {
|
||||
if (state.mode !== "gradient" || state.gradient.length <= 2) return false;
|
||||
state.gradient.splice(state.active, 1);
|
||||
state.active = Math.min(state.active, state.gradient.length - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
function setRgb(state, rgb) {
|
||||
if (rgb.length !== 3 || rgb.some((v) => !Number.isFinite(v) || v < 0 || v > 255)) return false;
|
||||
const color = activeColor(state);
|
||||
rgb.forEach((v, i) => { color[i] = Math.round(v); });
|
||||
return true;
|
||||
}
|
||||
|
||||
function setAlphaPercent(state, value) {
|
||||
if (!state.options.allow_alpha || !Number.isFinite(value) || value < 0 || value > 100) return false;
|
||||
activeColor(state)[3] = Math.round(value * 255 / 100);
|
||||
return true;
|
||||
}
|
||||
|
||||
function setHex(state, hex) {
|
||||
const color = parseHex(hex);
|
||||
if (!color) return false;
|
||||
if (hex.length === 7) color[3] = activeColor(state)[3];
|
||||
if (!state.options.allow_alpha) color[3] = 255;
|
||||
if (state.mode === "solid") state.solid = color;
|
||||
else state.gradient[state.active] = color;
|
||||
return true;
|
||||
}
|
||||
|
||||
function exportSelection(state) {
|
||||
const colors = state.mode === "solid" ? [state.solid] : state.gradient;
|
||||
return normalizeSelection({type: state.mode, colors: colors.map(toHex)}, state.options, state.maxGradientColors);
|
||||
}
|
||||
|
||||
// Favorites retain full capabilities regardless of what this caller can select.
|
||||
function normalizeFavorites(values) {
|
||||
if (!Array.isArray(values)) return [];
|
||||
return values.slice(0, 24).map((value) => normalizeSelection(value, {allow_gradient: true, allow_alpha: true}, MAX_GRADIENT_COLORS)).filter(Boolean);
|
||||
}
|
||||
|
||||
function selectionBackground(value) {
|
||||
return value.type === "gradient" ? `linear-gradient(to right, ${value.colors.join(", ")})` : `linear-gradient(${value.colors[0]}, ${value.colors[0]})`;
|
||||
}
|
||||
|
||||
return {MAX_GRADIENT_COLORS, NEW_GRADIENT_HUE_SHIFT, contrastColor, parseHex, toHex, normalizeSelection, rgbToHsl, hslToRgb, createState, applySelection, activeColor, setMode,
|
||||
addGradientColor, removeGradientColor, setRgb, setAlphaPercent, setHex, exportSelection, normalizeFavorites, selectionBackground};
|
||||
});
|
||||
@@ -1,314 +0,0 @@
|
||||
// Optional real-DOM regressions. Use an existing Playwright installation:
|
||||
// ORCA_PLAYWRIGHT_MODULE=<module path> ORCA_BROWSER_EXECUTABLE=<browser path>
|
||||
// ORCA_BROWSER_ENGINE=webkit exercises the macOS WebView layout engine.
|
||||
// node --test resources/web/dialog/ColorPickerDialog/colorpicker.browser.test.js
|
||||
const {test, before, after} = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const {readFileSync} = require("node:fs");
|
||||
const {resolve} = require("node:path");
|
||||
const {pathToFileURL} = require("node:url");
|
||||
const engineName = process.env.ORCA_BROWSER_ENGINE || "chromium";
|
||||
let engine;
|
||||
try { engine = require(process.env.ORCA_PLAYWRIGHT_MODULE || "playwright")[engineName]; }
|
||||
catch (error) { if (error.code !== "MODULE_NOT_FOUND") throw error; }
|
||||
const options = {skip: engine ? false : "Use an existing Playwright installation via ORCA_PLAYWRIGHT_MODULE"};
|
||||
const url = pathToFileURL(resolve(__dirname, "index.html")).href;
|
||||
let browser;
|
||||
before(async () => { if (engine) browser = await engine.launch({headless: true, ...(process.env.ORCA_BROWSER_EXECUTABLE ? {executablePath: process.env.ORCA_BROWSER_EXECUTABLE} : {})}); });
|
||||
after(async () => { if (browser) await browser.close(); });
|
||||
async function open(t, strings = {}, language = "") {
|
||||
const page = await browser.newPage({viewport: {width: 600, height: 660}}), errors = [];
|
||||
page.on("pageerror", error => errors.push(error.message));
|
||||
t.after(async () => { await page.close(); assert.deepEqual(errors, []); });
|
||||
await page.addInitScript(({table, language}) => {
|
||||
window.ORCA_UI_STRINGS = table;
|
||||
window.ORCA_COLOR_PICKER_LANGUAGE = language;
|
||||
window.messages = [];
|
||||
window.wx = {postMessage: text => messages.push(JSON.parse(text))};
|
||||
}, {table: strings, language});
|
||||
await page.goto(url);
|
||||
const ready = await page.evaluate(() => messages[0]);
|
||||
assert.equal(ready.command, "ready");
|
||||
return {page, ready, send: payload => page.evaluate(value => ColorPickerDialog.handleMessage(value), payload)};
|
||||
}
|
||||
function init(ready, gradient = false, alpha = false) {
|
||||
return {command: "init", page_id: ready.page_id, options: {allow_gradient: gradient, allow_alpha: alpha},
|
||||
selection: {type: "solid", colors: ["#01234567"]},
|
||||
ui: {sidebar_open: true, palette: "basic"},
|
||||
favorites: [{type: "gradient", colors: ["#FF000040", "#0000FF90"]}, {type: "solid", colors: ["#ABCDEF40"]}]};
|
||||
}
|
||||
async function count(page, command) { return page.evaluate(value => messages.filter(message => message.command === value).length, command); }
|
||||
|
||||
for (const gradient of [false, true]) for (const alpha of [false, true]) {
|
||||
test(`Native capabilities gate gradient=${gradient} and alpha=${alpha}`, options, async t => {
|
||||
const {page, ready, send} = await open(t);
|
||||
assert.equal(await page.locator("#confirmBtn").isDisabled(), true);
|
||||
assert.equal(await send({...init(ready), options: {allow_gradient: "yes", allow_alpha: alpha}}), false);
|
||||
assert.equal(await send({...init(ready), page_id: "stale"}), false);
|
||||
assert.equal(await send(init(ready, gradient, alpha)), true);
|
||||
assert.equal(await page.locator("#modeControls").isVisible(), gradient);
|
||||
assert.equal(await page.locator("#alphaControls").isVisible(), alpha);
|
||||
assert.equal(await page.locator("#userColors button:not(.empty)").count(), gradient ? 2 : 1);
|
||||
await page.locator("#userColors button:not(.empty)").last().click();
|
||||
const selection = await page.evaluate(() => ColorPickerDialog.exportSelection());
|
||||
assert.deepEqual(selection, {type: "solid", colors: [alpha ? "#ABCDEF40" : "#ABCDEFFF"]});
|
||||
assert.equal(await send(init(ready, gradient, alpha)), true);
|
||||
assert.deepEqual(await page.evaluate(() => ColorPickerDialog.exportSelection()), selection, "repeated ready/init retains edits");
|
||||
await page.locator("#colorPreview").click();
|
||||
const favorites = await page.evaluate(() => messages.at(-1).favorites);
|
||||
assert(favorites.some(favorite => favorite.colors[0] === "#ABCDEF40"), "original favorites retain their alpha");
|
||||
assert(favorites.some(favorite => favorite.type === "gradient"), "filtered gradients remain stored");
|
||||
await page.locator("#confirmBtn").click();
|
||||
assert.deepEqual(await page.evaluate(() => messages.at(-1).selection), selection);
|
||||
});
|
||||
}
|
||||
|
||||
test("Visible incomplete editors block confirmation and favorites until recovery", options, async t => {
|
||||
const {page, ready, send} = await open(t); await send(init(ready, true, true));
|
||||
for (const [id, value, valid] of [["rValue", "", "21"], ["oValue", "", "50"], ["hexInput", "GG", "12345680"]]) {
|
||||
await page.locator("#" + id).fill(value);
|
||||
const confirms = await count(page, "confirm"), saves = await count(page, "update_favorites");
|
||||
await page.locator("#confirmBtn").evaluate(element => element.click()); await page.locator("#colorPreview").click();
|
||||
assert.equal(await count(page, "confirm"), confirms); assert.equal(await count(page, "update_favorites"), saves);
|
||||
await page.locator("#" + id).fill(valid); await page.locator("#" + id).press("Enter");
|
||||
assert.equal(await count(page, "confirm"), confirms + 1);
|
||||
}
|
||||
await page.locator("#hValue").fill(""); // HSL is now visible beside RGB, so incomplete HSL must block.
|
||||
const hslConfirms = await count(page, "confirm");
|
||||
await page.locator("#confirmBtn").evaluate(element => element.click());
|
||||
assert.equal(await count(page, "confirm"), hslConfirms);
|
||||
await page.locator("#hValue").fill("100");
|
||||
const confirms = await count(page, "confirm"); await page.locator("#confirmBtn").click();
|
||||
assert.equal(await count(page, "confirm"), confirms + 1);
|
||||
await page.locator("#rValue").fill(""); await page.locator("#rValue").press("Escape");
|
||||
assert.equal(await page.evaluate(() => messages.at(-1).command), "cancel");
|
||||
});
|
||||
|
||||
test("Numeric channel and opacity edits clamp to their field bounds", options, async t => {
|
||||
const {page, ready, send} = await open(t); await send(init(ready, true, true));
|
||||
for (const [id, input, expected] of [["rValue", "999", "255"], ["rValue", "-1", "0"],
|
||||
["hValue", "361", "360"], ["oValue", "101", "100"], ["oValue", "-1", "0"]]) {
|
||||
await page.locator("#" + id).fill(input);
|
||||
assert.equal(await page.locator("#" + id).inputValue(), expected);
|
||||
assert.equal(await page.locator("#" + id).evaluate(element => element.checkValidity()), true);
|
||||
}
|
||||
assert.equal((await page.evaluate(() => ColorPickerDialog.exportSelection())).colors[0].slice(-2), "00");
|
||||
});
|
||||
|
||||
test("Endpoint edits and favorites retain ordered byte alpha", options, async t => {
|
||||
const {page, ready, send} = await open(t);
|
||||
await send({...init(ready, true, true), selection: {type: "gradient", colors: ["#FF000001", "#0000FF80"]}});
|
||||
await page.locator(".gradient-thumb[data-index='1']").click();
|
||||
await page.locator('.color-item[title="Red"]').first().click();
|
||||
assert.deepEqual(await page.evaluate(() => ColorPickerDialog.exportSelection()), {type: "gradient", colors: ["#FF000001", "#FF000080"]});
|
||||
await page.locator("#hexInput").fill("0000FF80"); await page.locator(".gradient-thumb[data-index='0']").click();
|
||||
assert.equal(await page.locator("#hexInput").inputValue(), "FF000001");
|
||||
await page.locator("#colorPreview").click();
|
||||
assert.deepEqual(await page.evaluate(() => messages.at(-1).favorites[0]), {type: "gradient", colors: ["#FF000001", "#0000FF80"]});
|
||||
});
|
||||
|
||||
test("Read-only favorites remain selectable and late documents cannot initialize", options, async t => {
|
||||
const {page, ready, send} = await open(t); await send({...init(ready), favorites_writable: false});
|
||||
assert.equal(await page.locator("#colorPreview").isDisabled(), true);
|
||||
await page.locator("#userColors button:not(.empty)").first().click();
|
||||
assert.equal(await page.locator("#hexInput").inputValue(), "ABCDEF");
|
||||
await page.reload();
|
||||
assert.equal(await send(init(ready)), false); assert.equal(await page.locator("#confirmBtn").isDisabled(), true);
|
||||
});
|
||||
|
||||
test("Document strings translate palette names and editor errors while retaining RAL codes and numeric labels", options, async t => {
|
||||
const source = readFileSync(resolve(__dirname, "../../../../src/slic3r/GUI/ColorPickerDialog.cpp"), "utf8");
|
||||
const keys = [...source.matchAll(/_u8L\("([^"\n]+)"\)/g)].map(match => match[1]);
|
||||
const strings = Object.fromEntries(keys.map(key => [key, "Translated: " + key]));
|
||||
const {page, ready, send} = await open(t, strings); await send({...init(ready, true, true), options: {allow_gradient: true, allow_alpha: true, max_gradient_colors: 7}, preserve_multi_color: true});
|
||||
assert.equal(await page.title(), strings["Color Picker"]);
|
||||
for (const element of await page.locator("[data-i18n]").all()) assert.equal(await element.textContent(), strings[await element.getAttribute("data-i18n")]);
|
||||
for (const attribute of ["aria-label", "title"]) for (const element of await page.locator(`[data-i18n-${attribute}]`).all())
|
||||
assert.equal((await element.getAttribute(attribute)).split("\n")[0], strings[await element.getAttribute(`data-i18n-${attribute}`)]);
|
||||
assert.equal(await page.locator("#selectionNotice").textContent(), strings["The current multi-color selection is kept until you choose a different color."]);
|
||||
for (const palette of ["basic", "orca", "ral", "gradient"]) {
|
||||
await page.selectOption("#paletteCombo", palette);
|
||||
const names = await page.evaluate(value => ColorPickerPalettes[value].map(entry => entry.name), palette);
|
||||
assert.deepEqual(await page.locator(".color-name").allTextContents(), names.map(name => {
|
||||
const ral = name.match(/^(\d{4}) (.+)$/); return ral ? `${ral[1]} ${strings[ral[2]]}` : /^%\d+$/.test(name) ? name : strings[name];
|
||||
}));
|
||||
}
|
||||
await page.locator("#hexInput").fill("bad");
|
||||
assert.equal(await page.locator("#hexInput").evaluate(element => element.validationMessage), strings["Enter a complete hexadecimal color"]);
|
||||
await page.locator("#hexInput").fill("12345680");
|
||||
for (const [id, invalid, valid, key] of [["rValue", "", "18", "Invalid input"],
|
||||
["oValue", "", "50", "Invalid input"]]) {
|
||||
const confirms = await count(page, "confirm");
|
||||
await page.locator("#" + id).fill(invalid);
|
||||
await page.locator("#confirmBtn").evaluate(element => element.click());
|
||||
await page.locator("#colorPreview").click(); // Saving invokes the same translated validation while OK is disabled.
|
||||
assert.equal(await count(page, "confirm"), confirms);
|
||||
assert.equal(await page.locator("#" + id).evaluate(element => element.validationMessage), strings[key]);
|
||||
await page.locator("#" + id).fill(valid);
|
||||
assert.equal(await page.locator("#" + id).evaluate(element => element.validationMessage), "");
|
||||
await page.locator("#confirmBtn").click();
|
||||
assert.equal(await count(page, "confirm"), confirms + 1);
|
||||
}
|
||||
await page.locator("#hValue").fill("361");
|
||||
await page.locator("#colorPreview").click();
|
||||
assert.equal(await page.locator("#hValue").inputValue(), "360", "out-of-range hue clamps at its boundary");
|
||||
await page.locator(".color-item").first().click();
|
||||
assert.equal(await page.locator("#hValue").evaluate(element => element.validationMessage), "", "a new palette selection clears the previous editor error");
|
||||
});
|
||||
|
||||
test("Document language and escaped labels preserve untranslated English fallback", options, async t => {
|
||||
const cancel = '取消 "Cancel" \\ /\n下一行';
|
||||
const {page, ready, send} = await open(t, {Cancel: cancel}, "zh-CN");
|
||||
await send(init(ready));
|
||||
assert.equal(await page.locator("html").getAttribute("lang"), "zh-CN");
|
||||
assert.equal(await page.locator("#cancelBtn").textContent(), cancel);
|
||||
assert.equal(await page.title(), "Color Picker");
|
||||
assert.equal(await page.locator(".color-name").first().textContent(), "Red");
|
||||
assert.deepEqual(await page.locator("label[for=hValue],label[for=sValue],label[for=lValue],label[for=rValue],label[for=gValue],label[for=bValue]").allTextContents(), ["H", "S", "L", "R", "G", "B"]);
|
||||
assert.deepEqual(await page.evaluate(() => ColorPickerDialog.exportSelection()), {type: "solid", colors: ["#012345FF"]});
|
||||
});
|
||||
|
||||
test("Global control colors follow the system while host semantic colors follow the app", options, async t => {
|
||||
const {page, ready, send} = await open(t); await send({...init(ready, true, true), selection: {type: "gradient", colors: ["#FF000001", "#0000FF80"]}});
|
||||
const initial = await page.evaluate(() => ({selection: ColorPickerDialog.exportSelection(), spectrum: document.getElementById("spectrum").toDataURL()}));
|
||||
for (const theme of ["light", "dark"]) for (const system of ["light", "dark"]) {
|
||||
await page.emulateMedia({colorScheme: system});
|
||||
await page.evaluate(value => {
|
||||
document.documentElement.dataset.orcaTheme = value;
|
||||
const colors = value === "light" ? {bg: "#ffffff", fg: "#262e30", muted: "#363636", border: "#dbdbdb", accent: "#009688"} : {bg: "#2d2d31", fg: "#efeff0", muted: "#b2b3b5", border: "#36363b", accent: "#00675b"};
|
||||
for (const [key, color] of Object.entries(colors)) document.documentElement.style.setProperty("--orca-" + key, color);
|
||||
document.documentElement.style.setProperty("--orca-accent-fg", "#ffffff");
|
||||
}, theme);
|
||||
await page.evaluate(async () => {
|
||||
await Promise.all(document.getAnimations().map(animation => animation.finished.catch(() => {})));
|
||||
});
|
||||
const colors = await page.evaluate(() => {
|
||||
const body = getComputedStyle(document.body);
|
||||
const tokenColor = token => {
|
||||
const probe = document.createElement("span"); probe.style.cssText = `position:absolute;visibility:hidden;color:var(${token})`;
|
||||
document.body.appendChild(probe); const color = getComputedStyle(probe).color; probe.remove(); return color;
|
||||
};
|
||||
const mode = document.querySelector("label[for=modeSwitch]");
|
||||
return {background: body.backgroundColor, foreground: body.color,
|
||||
modeBackground: getComputedStyle(mode).backgroundColor,
|
||||
accent: getComputedStyle(mode, "::before").backgroundColor,
|
||||
hostPanel: tokenColor("--panel"), hostText: tokenColor("--text")};
|
||||
});
|
||||
assert.equal(colors.background, system === "light" ? "rgb(255, 255, 255)" : "rgb(45, 45, 49)", `${theme} app / ${system} system body`);
|
||||
assert.equal(colors.foreground, system === "light" ? "rgb(38, 46, 48)" : "rgb(239, 239, 240)");
|
||||
assert.equal(colors.modeBackground, system === "light" ? "rgb(240, 240, 240)" : "rgb(36, 36, 40)");
|
||||
assert.equal(colors.accent, theme === "light" ? "rgb(0, 150, 136)" : "rgb(0, 103, 91)", "shared accent follows the injected app theme");
|
||||
assert.equal(colors.hostPanel, theme === "light" ? "rgb(255, 255, 255)" : "rgb(45, 45, 49)");
|
||||
assert.equal(colors.hostText, theme === "light" ? "rgb(38, 46, 48)" : "rgb(239, 239, 240)");
|
||||
assert.deepEqual(await page.evaluate(() => ({selection: ColorPickerDialog.exportSelection(), spectrum: document.getElementById("spectrum").toDataURL()})), initial);
|
||||
for (const id of ["modeSwitch"]) for (const checked of [false, true]) {
|
||||
if (await page.locator("#" + id).isChecked() !== checked) await page.locator(`label[for=${id}]`).click();
|
||||
await page.evaluate(async () => {
|
||||
await Promise.all(document.getAnimations().map(animation => animation.finished.catch(() => {})));
|
||||
});
|
||||
const result = await page.locator(`label[for=${id}]`).evaluate((element, selected) => {
|
||||
const tokenColor = token => {
|
||||
const probe = document.createElement("span"); probe.style.cssText = `position:absolute;visibility:hidden;color:var(${token})`;
|
||||
document.body.appendChild(probe); const color = getComputedStyle(probe).color; probe.remove(); return color;
|
||||
};
|
||||
const labels = element.querySelectorAll(".switch-label");
|
||||
return {selected: getComputedStyle(labels[selected ? 1 : 0]).color,
|
||||
unselected: getComputedStyle(labels[selected ? 0 : 1]).color,
|
||||
accentForeground: tokenColor("--button-fg-light"), mutedForeground: tokenColor("--fg-color-label")};
|
||||
}, checked);
|
||||
assert.equal(result.selected, result.accentForeground, `${theme} app / ${system} system ${id} selected text uses the accent foreground`);
|
||||
assert.equal(result.selected, "rgb(254, 254, 254)", "global selected text stays light in both system schemes");
|
||||
assert.equal(result.unselected, result.mutedForeground, `${theme} app / ${system} system ${id} unselected text stays muted`);
|
||||
assert.equal(result.unselected, system === "light" ? "rgb(54, 54, 54)" : "rgb(178, 179, 181)");
|
||||
const selected = await page.evaluate(() => ColorPickerDialog.exportSelection());
|
||||
assert.equal(selected.type, checked ? "gradient" : "solid");
|
||||
if (checked) assert.deepEqual(selected, initial.selection, "returning to gradient keeps ordered endpoint byte alpha");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("Sidebar resizes from a compact viewport and shrinks after every collapse", options, async t => {
|
||||
const {page, ready, send} = await open(t);
|
||||
await page.setViewportSize({width: 340, height: 380});
|
||||
await send({...init(ready, true, true), ui: {sidebar_open: false, palette: "basic"}});
|
||||
const selection = await page.evaluate(() => ColorPickerDialog.exportSelection());
|
||||
for (const [index, sidebarOpen] of [false, true, false, true, false].entries()) {
|
||||
const width = sidebarOpen ? 510 : 340;
|
||||
await page.waitForFunction(expected => messages.filter(message => message.command === "resize").at(-1)?.width === expected, width);
|
||||
const size = await page.evaluate(() => messages.filter(message => message.command === "resize").at(-1));
|
||||
assert.equal(size.height, 380);
|
||||
await page.setViewportSize({width: size.width, height: size.height});
|
||||
await page.waitForTimeout(100);
|
||||
assert(await page.evaluate(() => {
|
||||
const body = document.body.getBoundingClientRect();
|
||||
const picker = document.querySelector(".right-panel").getBoundingClientRect();
|
||||
return body.width === innerWidth && picker.right <= body.right && picker.bottom <= body.bottom;
|
||||
}), "the host viewport contains the full picker");
|
||||
const resizes = await count(page, "resize");
|
||||
await page.waitForTimeout(100);
|
||||
assert.equal(await count(page, "resize"), resizes, "fitting the host does not cause a resize loop");
|
||||
assert.deepEqual(await page.evaluate(() => ColorPickerDialog.exportSelection()), selection);
|
||||
if (index < 4) await page.locator("#paletteToggle").click();
|
||||
}
|
||||
});
|
||||
|
||||
test("Floating panel fits capabilities and wrapped translations without a resize feedback loop", options, async t => {
|
||||
const strings = {Color: "A long translated color label", Gradient: "A long translated gradient label", Cancel: "A longer translated cancel action", OK: "A longer translated confirmation action",
|
||||
"The current multi-color selection is kept until you choose a different color.": "A translated explanation that wraps across the compact floating panel and keeps the original multi-color selection until another color is chosen."};
|
||||
for (const gradient of [false, true]) for (const alpha of [false, true]) {
|
||||
const {page, ready, send} = await open(t, strings);
|
||||
await page.setViewportSize({width: 550, height: 520});
|
||||
await send({...init(ready, gradient, alpha), preserve_multi_color: true});
|
||||
await page.waitForFunction(() => messages.some(message => message.command === "resize"));
|
||||
const height = await page.evaluate(() => messages.filter(message => message.command === "resize").at(-1).height);
|
||||
assert(height >= 300 && height <= 900);
|
||||
assert.equal(height, await page.locator("body").evaluate(element => Math.ceil(element.getBoundingClientRect().height)));
|
||||
assert(await page.evaluate(() => messages.findIndex(message => message.command === "initialized") < messages.findIndex(message => message.command === "resize")));
|
||||
await page.setViewportSize({width: 550, height});
|
||||
await page.waitForTimeout(100);
|
||||
const resizes = await count(page, "resize");
|
||||
await page.setViewportSize({width: 550, height: height + 100});
|
||||
await page.waitForTimeout(100);
|
||||
assert.equal(await count(page, "resize"), resizes, "extra viewport height never changes intrinsic content height");
|
||||
const controls = await page.evaluate(() => {
|
||||
const body = document.body.getBoundingClientRect();
|
||||
return ["confirmBtn", "cancelBtn", ...(document.getElementById("alphaControls").classList.contains("hidden") ? [] : ["oValue"])].map(id => {
|
||||
const rect = document.getElementById(id).getBoundingClientRect();
|
||||
return rect.left >= body.left && rect.right <= body.right && rect.bottom <= body.bottom;
|
||||
});
|
||||
});
|
||||
assert(controls.every(Boolean), "wrapped buttons and opacity fit inside the panel");
|
||||
const edited = await page.evaluate(() => ColorPickerDialog.exportSelection());
|
||||
await page.evaluate(() => {
|
||||
document.getElementById("selectionNotice").textContent += " Additional wrapped text. ".repeat(15);
|
||||
});
|
||||
await page.waitForFunction(previous => messages.filter(message => message.command === "resize").length > previous, resizes);
|
||||
assert.deepEqual(await page.evaluate(() => ColorPickerDialog.exportSelection()), edited, "content resize leaves the selection untouched");
|
||||
// The compact layout now puts saved swatches to the right of both previews.
|
||||
assert(await page.evaluate(() => {
|
||||
const favorites = document.getElementById("userColors").getBoundingClientRect();
|
||||
const preview = document.getElementById("colorPreview").getBoundingClientRect();
|
||||
return favorites.left >= preview.right;
|
||||
}), "favorites do not overlap the preview");
|
||||
await page.locator("#confirmBtn").scrollIntoViewIfNeeded();
|
||||
assert.equal(await page.locator("#confirmBtn").isVisible(), true);
|
||||
}
|
||||
});
|
||||
|
||||
test("Native focus hook waits for initialization and preserves active partial edits", options, async t => {
|
||||
const {page, ready, send} = await open(t);
|
||||
await page.evaluate(() => ColorPickerDialog.focusInput());
|
||||
assert.notEqual(await page.evaluate(() => document.activeElement.id), "hexInput");
|
||||
await send(init(ready, true, true));
|
||||
await page.evaluate(() => ColorPickerDialog.focusInput());
|
||||
assert.equal(await page.evaluate(() => document.activeElement.id), "hexInput");
|
||||
await page.locator("#rValue").fill("");
|
||||
const selection = await page.evaluate(() => ColorPickerDialog.exportSelection());
|
||||
await page.evaluate(() => ColorPickerDialog.focusInput());
|
||||
assert.equal(await page.evaluate(() => document.activeElement.id), "rValue");
|
||||
assert.equal(await page.locator("#rValue").inputValue(), "");
|
||||
assert.deepEqual(await page.evaluate(() => ColorPickerDialog.exportSelection()), selection);
|
||||
assert.equal(await count(page, "confirm"), 0);
|
||||
await page.locator("#rValue").press("Escape");
|
||||
assert.equal(await count(page, "cancel"), 1);
|
||||
});
|
||||
@@ -1,464 +0,0 @@
|
||||
"use strict";
|
||||
(() => {
|
||||
if (window.ORCA_COLOR_PICKER_LANGUAGE) document.documentElement.lang = window.ORCA_COLOR_PICKER_LANGUAGE;
|
||||
const T = (key) => window.ORCA_UI_STRINGS?.[key] || key;
|
||||
document.title = T("Color Picker");
|
||||
for (const element of document.querySelectorAll("[data-i18n]")) element.textContent = T(element.dataset.i18n);
|
||||
for (const attribute of ["aria-label", "title", "placeholder"])
|
||||
for (const element of document.querySelectorAll(`[data-i18n-${attribute}]`))
|
||||
element.setAttribute(attribute, T(element.getAttribute(`data-i18n-${attribute}`)));
|
||||
function paletteName(name) {
|
||||
const ral = name.match(/^(\d{4}) (.+)$/);
|
||||
return ral ? `${ral[1]} ${T(ral[2])}` : /^%\d+$/.test(name) ? name : T(name);
|
||||
}
|
||||
|
||||
const FAVORITE_SLOTS = 16;
|
||||
const SPECTRUM_FOLLOWS_SATURATION = true; // true: the spectrum is drawn at the current saturation, false: it always shows full saturation
|
||||
const MULTI_COLOR_GRADIENTS = true; // true: gradients may have more than 2 colors (− / + buttons and the multi-color palette entries), false: exactly 2 colors.
|
||||
// up to the native limit the host sends as options.max_gradient_colors.
|
||||
let maxGradientColors = 2; // Stays 2 until init, and with a host that does not send a limit.
|
||||
|
||||
const model = window.ColorPickerModel;
|
||||
const palettes = window.ColorPickerPalettes;
|
||||
const byId = (id) => document.getElementById(id);
|
||||
const canvas = byId("spectrum"), context = canvas.getContext("2d");
|
||||
const paletteCombo = byId("paletteCombo"), palette = byId("palette"), preview = byId("colorPreview"), confirmBtn = byId("confirmBtn");
|
||||
const modeSwitch = byId("modeSwitch"), satSlider = byId("satSlider"), opacitySlider = byId("opacitySlider");
|
||||
const gradientBar = byId("gradientCtrlBar"), gradientStops = byId("gradientStops"), gradientAdd = byId("gradientAdd"), gradientRemove = byId("gradientRemove");
|
||||
const hostGradientLimit = (options) => options.max_gradient_colors === undefined ? 2 : options.max_gradient_colors;
|
||||
const originalPreview = byId("originalPreview"), palettePanel = byId("palettePanel"), paletteToggle = byId("paletteToggle");
|
||||
const paletteSelect = byId("paletteSelect"), paletteFilter = byId("paletteFilter"), paletteSearchToggle = byId("paletteSearchToggle");
|
||||
const rgbInputs = ["r", "g", "b"].map((name) => byId(name + "Value"));
|
||||
const hslInputs = ["h", "s", "l"].map((name) => byId(name + "Value"));
|
||||
const hexInput = byId("hexInput"), opacityInput = byId("oValue");
|
||||
const pageId = Date.now().toString(36) + Math.random().toString(36).slice(2);
|
||||
let state = model.createState(), favorites = [], initialized = false, favoritesWritable = true;
|
||||
// The selection the dialog opened with; the original swatch shows it and restores it on click.
|
||||
let originalSelection = model.exportSelection(state);
|
||||
let lastContentHeight = 0, lastContentWidth = 0, resizePending = false;
|
||||
// Last HSL shown or set. Kept while it still produces the current color: hue 360 and 0 are
|
||||
// both red, and greys, black and white have no hue or saturation in RGB.
|
||||
let hslMemory = [0, 0, 50];
|
||||
function displayHsl(color) {
|
||||
const rgb = model.hslToRgb(...hslMemory);
|
||||
if (!rgb.every((value, index) => value === color[index])) hslMemory = model.rgbToHsl(...color.slice(0, 3));
|
||||
return hslMemory;
|
||||
}
|
||||
// Copy of `values` with one entry replaced.
|
||||
const replaced = (values, index, value) => values.map((current, i) => i === index ? value : current);
|
||||
function setHslChannel(index, value) {
|
||||
hslMemory = replaced(displayHsl(model.activeColor(state)), index, value);
|
||||
model.setRgb(state, model.hslToRgb(...hslMemory));
|
||||
}
|
||||
const round1 = (value) => Math.round(value * 10) / 10;
|
||||
|
||||
function emit(command, data = {}) {
|
||||
window.dispatchEvent(new CustomEvent("color-picker-message", {detail: {command, ...data}}));
|
||||
if (window.wx && typeof window.wx.postMessage === "function")
|
||||
window.wx.postMessage(JSON.stringify({command, page_id: pageId, ...data}));
|
||||
}
|
||||
|
||||
function scheduleResize() {
|
||||
if (!initialized || resizePending) return;
|
||||
resizePending = true;
|
||||
requestAnimationFrame(() => {
|
||||
resizePending = false;
|
||||
const rect = document.body.getBoundingClientRect();
|
||||
const height = Math.min(900 , Math.max(300, Math.ceil(rect.height)));
|
||||
const width = Math.min(1200, Math.max(300, Math.ceil(rect.width )));
|
||||
if (height !== lastContentHeight || width !== lastContentWidth) {
|
||||
lastContentHeight = height; lastContentWidth = width;
|
||||
emit("resize", {width, height});
|
||||
}
|
||||
});
|
||||
}
|
||||
new ResizeObserver(scheduleResize).observe(document.body);
|
||||
|
||||
function setSwatch(element, selection) {
|
||||
element.style.setProperty("--swatch-color", model.selectionBackground(selection));
|
||||
}
|
||||
|
||||
// `editing` is the control the user is typing in. Its text is left alone so the caret,
|
||||
// partial input like "05" and fine steps that round-trip through bytes aren't overwritten.
|
||||
function render(resetEditors = false, editing = null) {
|
||||
const color = model.activeColor(state), hsl = displayHsl(color);
|
||||
const show = (input, value) => {
|
||||
if (input === editing) return;
|
||||
if (resetEditors) input.setCustomValidity("");
|
||||
if (resetEditors || input.checkValidity()) input.value = value;
|
||||
};
|
||||
show(hexInput, model.toHex(color).slice(1, state.options.allow_alpha ? 9 : 7));
|
||||
rgbInputs.forEach((input, index) => show(input, color[index]));
|
||||
// Shown in whole numbers; decimals can still be typed, and hslMemory keeps exact values.
|
||||
hslInputs.forEach((input, index) => show(input, Math.round(hsl[index])));
|
||||
// Percentage is a display only until explicitly edited. Never round it back
|
||||
// into the alpha byte on endpoint switches, RGB edits, or confirmation.
|
||||
show(opacityInput, Math.round(color[3] / 255 * 100));
|
||||
if (opacitySlider !== editing) opacitySlider.value = color[3] / 255 * 100;
|
||||
const rgb = color.slice(0, 3).join(",");
|
||||
opacitySlider.style.setProperty("--track-gradient", `linear-gradient(to right, rgba(${rgb},0), rgb(${rgb}))`);
|
||||
opacitySlider.style.setProperty("--thumb-color", `rgba(${rgb},${color[3] / 255})`);
|
||||
if (satSlider !== editing) satSlider.value = hsl[1];
|
||||
satSlider.style.setProperty("--track-gradient", `linear-gradient(to right, hsl(${hsl[0]},0%,${hsl[2]}%), hsl(${hsl[0]},100%,${hsl[2]}%))`);
|
||||
satSlider.style.setProperty("--thumb-color", `hsl(${hsl[0]},${hsl[1]}%,${hsl[2]}%)`);
|
||||
modeSwitch.checked = state.mode === "gradient";
|
||||
const selection = model.exportSelection(state);
|
||||
// Kept in the layout (visibility only) so the grid doesn't shift between Solid and Gradient.
|
||||
gradientBar.classList.toggle("invisible", state.mode !== "gradient");
|
||||
if (state.mode === "gradient") renderGradient(selection);
|
||||
setSwatch(preview, selection);
|
||||
preview.title = `${T("Save current color")}\n${selection.colors.join(" → ")}`;
|
||||
if (SPECTRUM_FOLLOWS_SATURATION && hsl[1] !== spectrumSaturation) { spectrumSaturation = hsl[1]; paintSpectrum(); }
|
||||
updateIndicator(hsl);
|
||||
updateConfirm();
|
||||
}
|
||||
|
||||
// One thumb per gradient color, evenly spaced; rebuilt only when the number of colors changes.
|
||||
function renderGradient(selection) {
|
||||
const count = selection.colors.length;
|
||||
setSwatch(gradientStops.querySelector(".gradient-track"), selection);
|
||||
let thumbs = [...gradientStops.querySelectorAll(".gradient-thumb")];
|
||||
if (thumbs.length !== count) {
|
||||
thumbs.forEach((thumb) => thumb.remove());
|
||||
thumbs = selection.colors.map((_, index) => {
|
||||
const thumb = document.createElement("button");
|
||||
thumb.type = "button"; thumb.className = "gradient-thumb"; thumb.dataset.index = index;
|
||||
thumb.style.setProperty("--stop", index / (count - 1));
|
||||
thumb.setAttribute("aria-label", count === 2 ? T(index ? "Gradient end" : "Gradient start") : `${T("Gradient color")} ${index + 1}`);
|
||||
gradientStops.appendChild(thumb);
|
||||
return thumb;
|
||||
});
|
||||
}
|
||||
thumbs.forEach((thumb, index) => {
|
||||
thumb.style.setProperty("--thumb-color", selection.colors[index]);
|
||||
thumb.setAttribute("aria-pressed", String(state.active === index));
|
||||
});
|
||||
|
||||
const multi = maxGradientColors > 2; // −/+ buttons only exist when more than 2 colors are allowed; − also needs a color to spare.
|
||||
gradientAdd.parentElement.classList.toggle("hidden", !multi);
|
||||
gradientRemove.classList.toggle("hidden", !multi || count <= 2);
|
||||
gradientAdd.classList.toggle("hidden", !multi);
|
||||
// aria-disabled instead of disabled: a disabled button gets no hover, so its tooltip would never show.
|
||||
const full = count >= maxGradientColors;
|
||||
gradientAdd.setAttribute("aria-disabled", String(full));
|
||||
gradientAdd.title = full ? T("Up to %d colors supported.").replace("%d", maxGradientColors) : T("Add color");
|
||||
}
|
||||
|
||||
// Every editor the user can see; alpha only when this caller allows it.
|
||||
function editors() {
|
||||
const inputs = [hexInput, ...rgbInputs, ...hslInputs];
|
||||
if (state.options.allow_alpha) inputs.push(opacityInput);
|
||||
return inputs;
|
||||
}
|
||||
// OK stays disabled while any visible editor holds an invalid or incomplete value.
|
||||
function updateConfirm() {
|
||||
confirmBtn.disabled = !editors().every((input) => input.checkValidity());
|
||||
}
|
||||
|
||||
// Matches the filter against the shown (translated) name, the English name and the hex digits.
|
||||
function paletteMatches(entry, query) {
|
||||
if (!query) return true;
|
||||
const haystack = [paletteName(entry.name), entry.name, entry.hex || "", entry.grad || ""].join(" ").toLowerCase();
|
||||
return query.split(/\s+/).every((word) => haystack.includes(word.replace(/^#/, "")));
|
||||
}
|
||||
|
||||
function populatePalette() {
|
||||
palette.replaceChildren();
|
||||
const query = paletteSearchToggle.getAttribute("aria-pressed") === "true" ? paletteFilter.value.trim().toLowerCase() : "";
|
||||
for (const entry of palettes[paletteCombo.value] || []) {
|
||||
const selection = {type: entry.grad ? "gradient" : "solid", colors: entry.grad ? entry.grad.split("-") : [entry.hex]};
|
||||
if (!model.normalizeSelection(selection, state.options, maxGradientColors) || !paletteMatches(entry, query)) continue;
|
||||
const row = document.createElement("button"); row.type = "button"; row.className = "color-item";
|
||||
const swatch = document.createElement("span"); swatch.className = "color-swatch";
|
||||
const name = document.createElement("span"); name.className = "color-name"; name.textContent = paletteName(entry.name);
|
||||
setSwatch(swatch, model.normalizeSelection(selection, {allow_gradient: true, allow_alpha: true}, maxGradientColors));
|
||||
row.append(swatch, name); row.title = paletteName(entry.name);
|
||||
row.addEventListener("click", () => {
|
||||
// Solid palette colors edit the active endpoint while gradient mode is on.
|
||||
const changed = entry.grad ? model.applySelection(state, selection) : model.setHex(state, entry.hex);
|
||||
if (changed) render(true);
|
||||
});
|
||||
palette.appendChild(row);
|
||||
}
|
||||
byId("paletteEmpty").classList.toggle("hidden", palette.childElementCount > 0);
|
||||
palette.classList.toggle("hidden", palette.childElementCount === 0);
|
||||
}
|
||||
|
||||
// Sidebar state the host keeps in the app config: open/closed and the chosen palette.
|
||||
const uiState = () => ({sidebar_open: palettePanel.classList.contains("open"), palette: paletteCombo.value});
|
||||
function setSidebarOpen(open) {
|
||||
palettePanel.classList.toggle("open", open);
|
||||
paletteToggle.setAttribute("aria-expanded", String(open));
|
||||
}
|
||||
// The search button swaps the combo box for a filter box; closing it clears the filter.
|
||||
function setPaletteSearch(open) {
|
||||
paletteSearchToggle.setAttribute("aria-pressed", String(open));
|
||||
paletteSelect.classList.toggle("hidden", open);
|
||||
paletteFilter.classList.toggle("hidden", !open);
|
||||
if (open) paletteFilter.focus();
|
||||
else paletteFilter.value = "";
|
||||
populatePalette();
|
||||
}
|
||||
|
||||
function drawFavorites() {
|
||||
const grid = byId("userColors"); grid.replaceChildren();
|
||||
// Favorites keep every color, but only those this page can select are shown.
|
||||
const visible = favorites.filter((favorite) => model.normalizeSelection(favorite, {...state.options, allow_alpha: true}, maxGradientColors));
|
||||
for (let index = 0; index < FAVORITE_SLOTS; ++index) {
|
||||
const swatch = document.createElement("button"); swatch.type = "button"; swatch.className = "saved-swatch";
|
||||
const favorite = visible[index];
|
||||
if (favorite) {
|
||||
setSwatch(swatch, favorite); swatch.title = favorite.colors.join(" → ");
|
||||
swatch.addEventListener("click", () => { if (model.applySelection(state, favorite)) render(true); });
|
||||
} else { swatch.classList.add("empty"); swatch.disabled = true; }
|
||||
grid.appendChild(swatch);
|
||||
}
|
||||
}
|
||||
|
||||
function init(payload) {
|
||||
if (!payload || typeof payload !== "object" || (payload.options !== undefined && (!payload.options || typeof payload.options !== "object"))) return false;
|
||||
maxGradientColors = Math.min(MULTI_COLOR_GRADIENTS ? model.MAX_GRADIENT_COLORS : 2, hostGradientLimit(payload.options || {}));
|
||||
// A gradient the host allows but this page does not (switch off) opens as its first color, like
|
||||
// other multi-color selections the picker cannot edit; OK then replaces it, Cancel keeps it.
|
||||
let selection = payload.selection, preserve = payload.preserve_multi_color === true;
|
||||
if (selection?.type === "gradient" && selection.colors?.length > maxGradientColors) {
|
||||
selection = {type: "solid", colors: [selection.colors[0]]};
|
||||
preserve = true;
|
||||
}
|
||||
const next = model.createState({...payload.options, max_gradient_colors: maxGradientColors}, selection);
|
||||
if (!next) return false;
|
||||
byId("selectionNotice").hidden = !preserve;
|
||||
state = next; favorites = model.normalizeFavorites(payload.favorites || []);
|
||||
originalSelection = model.exportSelection(state);
|
||||
setSwatch(originalPreview, originalSelection);
|
||||
originalPreview.title = `${T("Original color")}\n${originalSelection.colors.join(" → ")}`;
|
||||
favoritesWritable = payload.favorites_writable !== false;
|
||||
preview.disabled = !favoritesWritable;
|
||||
byId("modeControls").classList.toggle("hidden", !state.options.allow_gradient);
|
||||
for (const option of paletteCombo.options) option.hidden = option.value === "gradient" && !state.options.allow_gradient;
|
||||
const ui = payload.ui || {}; // Restore the sidebar the way it was left; a palette this caller can't use falls back to basic.
|
||||
const saved = [...paletteCombo.options].find((option) => option.value === ui.palette && !option.hidden);
|
||||
if (saved) paletteCombo.value = saved.value;
|
||||
if (!state.options.allow_gradient && paletteCombo.value === "gradient") paletteCombo.value = "basic";
|
||||
setSidebarOpen(ui.sidebar_open === true);
|
||||
byId("alphaControls").classList.toggle("hidden", !state.options.allow_alpha);
|
||||
hexInput.maxLength = state.options.allow_alpha ? 9 : 7;
|
||||
populatePalette(); drawFavorites(); render(true);
|
||||
initialized = true;
|
||||
byId("pickerEditors").disabled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function focusInput() {
|
||||
if (!initialized) return;
|
||||
// Restore initial keyboard focus without replacing an editor's in-progress input.
|
||||
const active = document.activeElement;
|
||||
if (!active || active === document.body || active === document.documentElement)
|
||||
hexInput.focus({preventScroll: true});
|
||||
}
|
||||
|
||||
function handleMessage(payload) {
|
||||
if (!payload || typeof payload !== "object" || payload.page_id !== pageId) return false;
|
||||
if (payload.command === "init") {
|
||||
const options = payload.options;
|
||||
const hostLimit = options && hostGradientLimit(options);
|
||||
if (!options || typeof options.allow_gradient !== "boolean" || typeof options.allow_alpha !== "boolean" ||
|
||||
!Number.isInteger(hostLimit) || hostLimit < 2 || hostLimit > model.MAX_GRADIENT_COLORS ||
|
||||
!model.normalizeSelection(payload.selection, options, hostLimit) || !validFavorites(payload.favorites) ||
|
||||
(payload.favorites_writable !== undefined && typeof payload.favorites_writable !== "boolean") ||
|
||||
(payload.preserve_multi_color !== undefined && typeof payload.preserve_multi_color !== "boolean") ||
|
||||
(payload.ui !== undefined && (!payload.ui || typeof payload.ui !== "object" ||
|
||||
(payload.ui.sidebar_open !== undefined && typeof payload.ui.sidebar_open !== "boolean") ||
|
||||
(payload.ui.palette !== undefined && typeof payload.ui.palette !== "string")))) return false;
|
||||
if (initialized) return true;
|
||||
if (!init(payload)) return false;
|
||||
emit("initialized");
|
||||
scheduleResize();
|
||||
return true;
|
||||
}
|
||||
if (payload.command === "favorites" && initialized && validFavorites(payload.favorites)) {
|
||||
favorites = model.normalizeFavorites(payload.favorites); drawFavorites(); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function validFavorites(values) {
|
||||
return Array.isArray(values) && values.length <= 24 && values.every((value) => model.normalizeSelection(value, {allow_gradient: true, allow_alpha: true}, model.MAX_GRADIENT_COLORS));
|
||||
}
|
||||
|
||||
// Saturation the spectrum is currently painted at (always 100 when the option is off).
|
||||
let spectrumSaturation = 100;
|
||||
function drawSpectrum() {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.width = Math.max(1, Math.round(rect.width)); canvas.height = Math.max(1, Math.round(rect.height));
|
||||
paintSpectrum();
|
||||
}
|
||||
function paintSpectrum() {
|
||||
const hue = context.createLinearGradient(0, 0, canvas.width, 0);
|
||||
for (let i = 0; i <= 6; ++i) hue.addColorStop(i / 6, `hsl(${i * 60},100%,50%)`);
|
||||
context.fillStyle = hue; context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
const shade = context.createLinearGradient(0, 0, 0, canvas.height);
|
||||
shade.addColorStop(0, "#FFFFFF"); shade.addColorStop(0.5, "#FFFFFF00"); shade.addColorStop(0.5, "#00000000"); shade.addColorStop(1, "#000000");
|
||||
context.fillStyle = shade; context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
// HSL desaturation blends each color toward the grey of the same lightness, and on this
|
||||
// canvas that grey only depends on the row (white at the top, black at the bottom).
|
||||
if (spectrumSaturation < 100) {
|
||||
const grey = context.createLinearGradient(0, 0, 0, canvas.height);
|
||||
grey.addColorStop(0, "#FFFFFF"); grey.addColorStop(1, "#000000");
|
||||
context.globalAlpha = 1 - spectrumSaturation / 100;
|
||||
context.fillStyle = grey; context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
context.globalAlpha = 1;
|
||||
}
|
||||
}
|
||||
|
||||
const indicator = byId("colorIndicator");
|
||||
function updateIndicator(hsl) {
|
||||
indicator.style.left = hsl[0] / 360 * (canvas.width - 1) + "px";
|
||||
indicator.style.top = (1 - hsl[2] / 100) * (canvas.height - 1) + "px";
|
||||
}
|
||||
|
||||
// Spectrum position maps straight to hue (x, 0..360) and lightness (y, 100..0); saturation is
|
||||
// the third dimension and comes from the slider below. hslMemory keeps these exact values,
|
||||
// including hue 360 at the right edge and the hue of the white and black corners.
|
||||
function sampleSpectrum(event) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const fraction = (client, start, size, pixels) =>
|
||||
Math.max(0, Math.min(pixels - 1, Math.floor((client - start) * pixels / size))) / Math.max(1, pixels - 1);
|
||||
const saturation = displayHsl(model.activeColor(state))[1];
|
||||
hslMemory = [round1(fraction(event.clientX, rect.left, rect.width, canvas.width) * 360), saturation,
|
||||
round1((1 - fraction(event.clientY, rect.top, rect.height, canvas.height)) * 100)];
|
||||
model.setRgb(state, model.hslToRgb(...hslMemory));
|
||||
// A pick replaces every editor's value, so clear any invalid state left from typing.
|
||||
render(true);
|
||||
}
|
||||
|
||||
const clamp = (control, value) => Math.min(Number(control.max), Math.max(Number(control.min), value));
|
||||
function bindValue(control, apply) {
|
||||
// Number fields are required, so an empty field is invalid; a range input always has a value.
|
||||
if (control.type === "number") control.required = true;
|
||||
const commit = (value) => { apply(value); render(false, control); };
|
||||
control.addEventListener("input", () => {
|
||||
control.setCustomValidity("");
|
||||
// Out-of-range numbers snap to the field's min/max instead of becoming invalid.
|
||||
if (control.value !== "") {
|
||||
const clamped = clamp(control, Number(control.value));
|
||||
if (clamped !== Number(control.value)) control.value = clamped;
|
||||
}
|
||||
if (control.checkValidity()) commit(Number(control.value));
|
||||
});
|
||||
control.addEventListener("wheel", (event) => {
|
||||
event.preventDefault();
|
||||
const step = control.type === "range" ? 5 : 1;
|
||||
control.value = clamp(control, (Number(control.value) || 0) + (event.deltaY > 0 ? -step : step));
|
||||
control.setCustomValidity("");
|
||||
commit(Number(control.value));
|
||||
}, {passive: false});
|
||||
// Normalize the text (e.g. "05" → "5") once the user leaves the field.
|
||||
control.addEventListener("blur", () => { if (control.checkValidity()) render(); });
|
||||
}
|
||||
|
||||
rgbInputs.forEach((input, index) => bindValue(input, (value) => model.setRgb(state, replaced(model.activeColor(state).slice(0, 3), index, value))));
|
||||
hslInputs.forEach((input, index) => bindValue(input, (value) => setHslChannel(index, value)));
|
||||
bindValue(satSlider, (value) => setHslChannel(1, value));
|
||||
bindValue(opacityInput, (value) => model.setAlphaPercent(state, value));
|
||||
bindValue(opacitySlider, (value) => model.setAlphaPercent(state, value));
|
||||
// The opacity slider takes the gradient bar's row while the opacity field or the slider is in
|
||||
// use: it opens when the field is clicked or focused, and closes on a click or focus elsewhere.
|
||||
const thirdRow = byId("thirdRow"), alphaControls = byId("alphaControls");
|
||||
const inOpacityControls = (target) => target instanceof Element && target.closest("#alphaControls, #opacitySliderBar");
|
||||
const setOpacitySliderOpen = (open) => thirdRow.classList.toggle("opacity-open", open);
|
||||
for (const type of ["focusin", "click"]) alphaControls.addEventListener(type, () => setOpacitySliderOpen(true));
|
||||
for (const type of ["pointerdown", "focusin"])
|
||||
document.addEventListener(type, (event) => { if (!inOpacityControls(event.target)) setOpacitySliderOpen(false); });
|
||||
hexInput.addEventListener("input", () => {
|
||||
const text = hexInput.value;
|
||||
const valid = model.setHex(state, text.startsWith("#") ? text : "#" + text);
|
||||
hexInput.setCustomValidity(valid ? "" : T("Enter a complete hexadecimal color"));
|
||||
// render() leaves the typed text alone, so alpha isn't appended after the sixth digit.
|
||||
if (valid) render(false, hexInput);
|
||||
});
|
||||
hexInput.addEventListener("blur", () => { if (hexInput.checkValidity()) render(); });
|
||||
modeSwitch.addEventListener("change", () => {
|
||||
|
||||
setOpacitySliderOpen(false); // Close the opacity slider before the row changes, so it doesn't linger over the gradient bar.
|
||||
model.setMode(state, modeSwitch.checked ? "gradient" : "solid");
|
||||
render(true);
|
||||
});
|
||||
// Click on the track selects the nearest thumb. Keyboard users tab to a thumb and press Enter or Space.
|
||||
gradientStops.addEventListener("click", (event) => {
|
||||
if (state.mode !== "gradient") return;
|
||||
// A thumb is read from its index: a keyboard-triggered click has no pointer position.
|
||||
const thumb = event.target.closest(".gradient-thumb"), rect = gradientStops.getBoundingClientRect();
|
||||
const last = state.gradient.length - 1;
|
||||
// Thumbs sit at 10px + (width - 20px) * i / last, matching the CSS.
|
||||
const fraction = Math.max(0, Math.min(1, (event.clientX - rect.left - 10) / Math.max(1, rect.width - 20)));
|
||||
const index = thumb ? Number(thumb.dataset.index) : Math.round(fraction * last);
|
||||
if (index !== state.active) { state.active = index; render(true); }
|
||||
});
|
||||
gradientAdd.addEventListener("click", () => { if (gradientAdd.getAttribute("aria-disabled") !== "true" && model.addGradientColor(state)) render(true); });
|
||||
gradientRemove.addEventListener("click", () => { if (model.removeGradientColor(state)) render(true); });
|
||||
originalPreview.addEventListener("click", () => { if (model.applySelection(state, originalSelection)) render(true); });
|
||||
paletteToggle.addEventListener("click", () => {
|
||||
setSidebarOpen(!palettePanel.classList.contains("open"));
|
||||
if (initialized) emit("update_ui", uiState());
|
||||
});
|
||||
paletteCombo.addEventListener("change", () => {
|
||||
populatePalette();
|
||||
if (initialized) emit("update_ui", uiState());
|
||||
});
|
||||
paletteSearchToggle.addEventListener("click", () => setPaletteSearch(paletteSearchToggle.getAttribute("aria-pressed") !== "true"));
|
||||
paletteFilter.addEventListener("input", populatePalette);
|
||||
// Pointer capture ends by itself on pointerup/pointercancel, so it doubles as the drag state.
|
||||
canvas.addEventListener("pointerdown", (event) => { canvas.setPointerCapture(event.pointerId); sampleSpectrum(event); });
|
||||
canvas.addEventListener("pointermove", (event) => { if (canvas.hasPointerCapture(event.pointerId)) sampleSpectrum(event); });
|
||||
// The spectrum takes the panel's free height, which also changes with the notice, alpha and
|
||||
// gradient controls, so redraw on any size change rather than on window resize only.
|
||||
new ResizeObserver(() => { drawSpectrum(); updateIndicator(displayHsl(model.activeColor(state))); }).observe(canvas.parentElement);
|
||||
// Bubble phase on document runs after each editor's own handler has set its validity.
|
||||
document.addEventListener("input", updateConfirm);
|
||||
// Hex keeps its own message from its input handler; number fields get a translated one.
|
||||
function validateEditors() {
|
||||
const inputs = editors();
|
||||
for (const input of inputs.filter((input) => input !== hexInput)) {
|
||||
input.setCustomValidity("");
|
||||
const {valid, rangeUnderflow, rangeOverflow} = input.validity;
|
||||
if (!valid) input.setCustomValidity(T(rangeUnderflow || rangeOverflow ? "Value is out of range." : "Invalid input"));
|
||||
}
|
||||
return inputs.every((input) => input.reportValidity());
|
||||
}
|
||||
function confirm() {
|
||||
if (initialized && validateEditors()) emit("confirm", {selection: model.exportSelection(state)});
|
||||
}
|
||||
preview.addEventListener("click", () => {
|
||||
if (!initialized || !favoritesWritable || !validateEditors()) return;
|
||||
const selection = model.exportSelection(state);
|
||||
const key = JSON.stringify(selection);
|
||||
favorites = [selection, ...favorites.filter((favorite) => JSON.stringify(favorite) !== key)].slice(0, FAVORITE_SLOTS);
|
||||
drawFavorites(); emit("update_favorites", {favorites});
|
||||
});
|
||||
confirmBtn.addEventListener("click", confirm);
|
||||
byId("cancelBtn").addEventListener("click", () => emit("cancel"));
|
||||
document.addEventListener("keydown", (event) => {
|
||||
// Escape in the filter box closes the search first; a second Escape cancels the dialog.
|
||||
if (event.key === "Escape" && event.target === paletteFilter) { event.preventDefault(); setPaletteSearch(false); paletteSearchToggle.focus(); }
|
||||
else if (event.key === "Escape") { event.preventDefault(); emit("cancel"); }
|
||||
else if (event.key === "Enter" && event.target instanceof HTMLInputElement && ["text", "number"].includes(event.target.type)) {
|
||||
event.preventDefault(); confirm();
|
||||
}
|
||||
});
|
||||
|
||||
window.ColorPickerDialog = {
|
||||
init, handleMessage, focusInput, exportSelection: () => model.exportSelection(state),
|
||||
setFavorites: (values) => { favorites = model.normalizeFavorites(values); drawFavorites(); }
|
||||
};
|
||||
// The host passes the saved sidebar state before the page loads (the same values come again in
|
||||
// init), so the first paint already matches the window size the host opened with.
|
||||
const earlyUi = window.ORCA_COLOR_PICKER_UI;
|
||||
if (earlyUi && typeof earlyUi === "object") {
|
||||
if (typeof earlyUi.palette === "string" && [...paletteCombo.options].some((option) => option.value === earlyUi.palette))
|
||||
paletteCombo.value = earlyUi.palette;
|
||||
if (typeof earlyUi.sidebar_open === "boolean") setSidebarOpen(earlyUi.sidebar_open);
|
||||
}
|
||||
setSwatch(originalPreview, originalSelection);
|
||||
drawSpectrum(); populatePalette(); drawFavorites(); render(true); emit("ready");
|
||||
})();
|
||||
@@ -1,103 +0,0 @@
|
||||
"use strict";
|
||||
const {test} = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const model = require("./colorpicker-model.js");
|
||||
|
||||
test("complete RGB/RGBA hex accepts mixed case and rejects partial or unbounded input", () => {
|
||||
assert.deepEqual(model.parseHex("#Ab12cD80"), [171,18,205,128]);
|
||||
for (const hex of ["", "123456", "#123", "#1234567", "#123456789", "#12345Z", "#123456GG", " #123456", null]) assert.equal(model.parseHex(hex), null);
|
||||
for (let byte = 0; byte <= 255; ++byte) {
|
||||
const hex = model.toHex([byte, byte, byte, byte]);
|
||||
assert.deepEqual(model.parseHex(hex), [byte,byte,byte,byte]);
|
||||
}
|
||||
});
|
||||
|
||||
test("HSL conversion preserves primary channels and hue wraps at 360", () => {
|
||||
for (const rgb of [[255,0,0], [0,255,0], [0,0,255], [128,128,128], [37,129,240]]) assert.deepEqual(model.hslToRgb(...model.rgbToHsl(...rgb)), rgb);
|
||||
assert.deepEqual(model.hslToRgb(360,100,50), [255,0,0]);
|
||||
assert.deepEqual(model.hslToRgb(240,100,50), [0,0,255]);
|
||||
});
|
||||
|
||||
test("capabilities default off and reject gradient through every model entry", () => {
|
||||
const gradient = {type: "gradient", colors: ["#FF000080", "#0000FF20"]};
|
||||
const state = model.createState();
|
||||
assert.deepEqual(state.options, {allow_gradient: false, allow_alpha: false});
|
||||
assert.equal(model.createState({}, gradient), null);
|
||||
assert.equal(model.applySelection(state, gradient), false);
|
||||
assert.equal(model.setMode(state, "gradient"), false);
|
||||
assert.equal(model.setAlphaPercent(state, 20), false);
|
||||
assert.equal(model.createState({allow_alpha: true}, gradient), null);
|
||||
const opaque = model.createState({allow_gradient: true}, gradient);
|
||||
assert.deepEqual(model.exportSelection(opaque), {type: "gradient", colors: ["#FF0000FF", "#0000FFFF"]});
|
||||
});
|
||||
|
||||
test("gradient endpoint edits keep order and preserve unedited byte alpha", () => {
|
||||
const initial = {type: "gradient", colors: ["#FF000001", "#0000FF80"]};
|
||||
const state = model.createState({allow_gradient: true, allow_alpha: true}, initial);
|
||||
state.active = 0; model.setRgb(state, [0,255,0]);
|
||||
state.active = 1; model.setHex(state, "#112233");
|
||||
assert.deepEqual(model.exportSelection(state), {type: "gradient", colors: ["#00FF0001", "#11223380"]});
|
||||
model.setAlphaPercent(state, 0);
|
||||
assert.deepEqual(model.exportSelection(state).colors, ["#00FF0001", "#11223300"]);
|
||||
model.setAlphaPercent(state, 100);
|
||||
assert.equal(model.exportSelection(state).colors[1], "#112233FF");
|
||||
assert.deepEqual(initial.colors, ["#FF000001", "#0000FF80"]);
|
||||
});
|
||||
|
||||
test("favorites retain alpha and gradient even when the selection disables them", () => {
|
||||
const favorite = {type: "solid", colors: ["#ABCDEF80"]};
|
||||
const gradient = {type: "gradient", colors: ["#FFFFFF00", "#00000010"]};
|
||||
const favorites = model.normalizeFavorites([favorite, gradient]);
|
||||
const state = model.createState();
|
||||
assert.equal(model.applySelection(state, favorites[0]), true);
|
||||
assert.deepEqual(model.exportSelection(state), {type: "solid", colors: ["#ABCDEFFF"]});
|
||||
assert.deepEqual(favorites, [favorite, gradient]);
|
||||
assert.equal(model.applySelection(state, favorites[1]), false);
|
||||
assert.equal(model.normalizeFavorites(Array(25).fill(favorite)).length, 24);
|
||||
});
|
||||
|
||||
test("invalid cardinality and channel edits cannot change a selection", () => {
|
||||
const state = model.createState({allow_alpha: true});
|
||||
const before = model.exportSelection(state);
|
||||
for (const value of [{type: "solid", colors: []}, {type: "solid", colors: ["#112233", "#445566"]}, {type: "gradient", colors: ["#112233"]}, {type: "other", colors: ["#112233"]}]) assert.equal(model.applySelection(state, value), false);
|
||||
assert.equal(model.setHex(state, "#GG2233"), false);
|
||||
for (const value of [-1, 101, NaN, Infinity]) assert.equal(model.setAlphaPercent(state, value), false);
|
||||
assert.equal(model.setRgb(state, [256,0,0]), false);
|
||||
assert.deepEqual(model.exportSelection(state), before);
|
||||
});
|
||||
|
||||
test("multi-color gradients stay at 2 colors unless the caller raises the limit", () => {
|
||||
const five = {type: "gradient", colors: ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#00FFFF"]};
|
||||
assert.equal(model.normalizeSelection(five, {allow_gradient: true}), null);
|
||||
assert.equal(model.createState({allow_gradient: true}, five), null);
|
||||
const state = model.createState({allow_gradient: true, max_gradient_colors: 7}, five);
|
||||
assert.equal(state.gradient.length, 5);
|
||||
assert.equal(model.normalizeSelection({...five, colors: Array(8).fill("#000000")}, {allow_gradient: true}, 7), null);
|
||||
assert.equal(model.createState({allow_gradient: true, max_gradient_colors: 99}).maxGradientColors, model.MAX_GRADIENT_COLORS);
|
||||
assert.equal(model.normalizeFavorites([five]).length, 1, "favorites keep every color");
|
||||
});
|
||||
|
||||
test("adding and removing gradient colors keeps order, selection and the 2..max limits", () => {
|
||||
const state = model.createState({allow_gradient: true, allow_alpha: true, max_gradient_colors: 4}, {type: "gradient", colors: ["#FF000080", "#0000FFFF"]});
|
||||
assert.equal(model.removeGradientColor(state), false, "never fewer than 2");
|
||||
assert.equal(model.addGradientColor(state), true);
|
||||
assert.deepEqual(model.exportSelection(state).colors, ["#FF000080", "#BFFF0080", "#0000FFFF"], "selected color turned 75° in hue, alpha kept");
|
||||
assert.equal(model.NEW_GRADIENT_HUE_SHIFT, 75);
|
||||
assert.equal(state.active, 1);
|
||||
state.active = 2; assert.equal(model.addGradientColor(state), true);
|
||||
assert.equal(state.active, 3); assert.equal(state.gradient.length, 4);
|
||||
assert.equal(model.addGradientColor(state), false, "limit reached");
|
||||
state.active = 1; assert.equal(model.removeGradientColor(state), true);
|
||||
assert.deepEqual(model.exportSelection(state).colors.slice(0, 2), ["#FF000080", "#0000FFFF"]);
|
||||
assert.equal(state.active, 1);
|
||||
assert.equal(model.applySelection(state, {type: "gradient", colors: ["#111111", "#222222"]}), true);
|
||||
state.active = 1; model.addGradientColor(state); model.addGradientColor(state);
|
||||
model.applySelection(state, {type: "gradient", colors: ["#111111", "#222222"]});
|
||||
assert.equal(state.active, 1, "a shorter gradient clamps the selected color");
|
||||
});
|
||||
|
||||
test("a new gradient starts at the solid color and its 75° hue neighbour", () => {
|
||||
const state = model.createState({allow_gradient: true, allow_alpha: true}, {type: "solid", colors: ["#FF000080"]});
|
||||
assert.equal(model.setMode(state, "gradient"), true);
|
||||
assert.deepEqual(model.exportSelection(state).colors, ["#FF000080", "#BFFF0080"]);
|
||||
});
|
||||
@@ -1,154 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-orca-page="color-picker">
|
||||
<head><title data-i18n="Color Picker">Color Picker</title>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="../../include/global.css">
|
||||
<link rel="stylesheet" href="../css/theme.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<p id="selectionNotice" hidden data-i18n="The current multi-color selection is kept until you choose a different color.">The current multi-color selection is kept until you choose a different color.</p>
|
||||
<fieldset id="pickerEditors" disabled>
|
||||
<div class="main-wrapper">
|
||||
|
||||
<div class="left-panel" id="palettePanel">
|
||||
<div class="palette thin-scroll" id="palette" tabindex="-1"></div>
|
||||
<p class="palette-empty hidden" id="paletteEmpty" data-i18n="No matching colors">No matching colors</p>
|
||||
<div class="palette-toolbar">
|
||||
<div class="palette-select ComboBox" id="paletteSelect">
|
||||
<div class="arrow-icon"></div>
|
||||
<select id="paletteCombo" tabindex="-1">
|
||||
<option value="basic" data-i18n="Basic Colors">Basic Colors</option>
|
||||
<option value="orca" data-i18n="Orca Palette">Orca Palette</option>
|
||||
<option value="ral" data-i18n="RAL Classic" >RAL Classic</option>
|
||||
<option value="gradient" hidden data-i18n="Gradients" >Gradients</option>
|
||||
</select>
|
||||
</div>
|
||||
<input type="search" id="paletteFilter" class="palette-filter hidden" autocomplete="off" spellcheck="false"
|
||||
placeholder="Filter colors" data-i18n-placeholder="Filter colors" aria-label="Filter colors" data-i18n-aria-label="Filter colors">
|
||||
<button type="button" id="paletteSearchToggle" class="palette-search-button" aria-pressed="false" title="Search colors" data-i18n-title="Search colors">
|
||||
<div class="icon-search icon16"></div>
|
||||
<div class="icon-close icon16"></div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-panel">
|
||||
<div class="spectrum-row">
|
||||
<div class="saturation-slider">
|
||||
<input class="valueSlider" type="range" id="satSlider" min="0" max="100" step="0.1" tabindex="-1" aria-label="Saturation" data-i18n-aria-label="Saturation">
|
||||
</div>
|
||||
<div class="spectrum-container">
|
||||
<canvas id="spectrum"></canvas>
|
||||
<div id="colorIndicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-grid">
|
||||
<div class="editor-column">
|
||||
<div id="modeControls" class="hidden">
|
||||
<input class="switch-check" type="checkbox" id="modeSwitch">
|
||||
<label class="switch-slider" for="modeSwitch">
|
||||
<div class="switch-label" data-i18n="Solid">Solid</div>
|
||||
<div class="switch-label" data-i18n="Gradient">Gradient</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="hex-row">
|
||||
<div class="inline-field field-hex">
|
||||
<label for="hexInput">#</label>
|
||||
<input type="text" id="hexInput" required value="808080" maxlength="8" autocomplete="off" spellcheck="false" aria-label="Hex color" data-i18n-aria-label="Hex color">
|
||||
</div>
|
||||
<div class="inline-field value-field hidden" id="alphaControls" title="Opacity percent">
|
||||
<label for="oValue">
|
||||
<svg viewBox="0 0 12 12" width="12" height="12">
|
||||
<path d="M0,1v10c0,.55.45,1,1,1h10c.55,0,1-.45,1-1V1c0-.55-.45-1-1-1H1C.45,0,0,.45,0,1ZM6,6v5H1v-5h5V1h5v5h-5Z" style="fill:#999"/>
|
||||
</svg>
|
||||
</label>
|
||||
<input type="number" id="oValue" min="0" max="100" aria-label="Opacity percent" data-i18n-aria-label="Opacity percent">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Third row: gradient endpoints, replaced by the opacity slider while the opacity field is in use. -->
|
||||
<div id="thirdRow" class="third-row">
|
||||
<!-- Gradient colors: evenly spaced thumbs (built by colorpicker.js); a click selects the nearest. -->
|
||||
<div id="gradientCtrlBar" class="invisible" role="group" aria-label="Gradient" data-i18n-aria-label="Gradient">
|
||||
<div id="gradientStops" class="gradient-stops">
|
||||
<div class="gradient-track"></div>
|
||||
</div>
|
||||
<div class="gradient-step-buttons hidden">
|
||||
<button type="button" id="gradientRemove" class="gradient-step-button hidden" title="Remove color" data-i18n-title="Remove color">
|
||||
<svg viewBox="0 0 10 10" width="10" height="10" aria-hidden="true"><rect x="0" y="4" width="10" height="2" fill="currentColor"/></svg>
|
||||
</button>
|
||||
<button type="button" id="gradientAdd" class="gradient-step-button hidden" title="Add color" data-i18n-title="Add color">
|
||||
<svg viewBox="0 0 10 10" width="10" height="10" aria-hidden="true"><rect x="0" y="4" width="10" height="2" fill="currentColor"/><rect x="4" y="0" width="2" height="10" fill="currentColor"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="opacitySliderBar" hidden>
|
||||
<input class="valueSlider" type="range" id="opacitySlider" min="0" max="100" step="0.1" tabindex="-1" aria-label="Opacity" data-i18n-aria-label="Opacity">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-column">
|
||||
<div class="inline-field value-field" title="Hue">
|
||||
<label for="hValue">H</label>
|
||||
<input type="number" id="hValue" min="0" max="360" step="1">
|
||||
</div>
|
||||
<div class="inline-field value-field" title="Saturation">
|
||||
<label for="sValue">S</label>
|
||||
<input type="number" id="sValue" min="0" max="100" step="1">
|
||||
</div>
|
||||
<div class="inline-field value-field" title="Lightness">
|
||||
<label for="lValue">L</label>
|
||||
<input type="number" id="lValue" min="0" max="100" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-column">
|
||||
<div class="inline-field value-field" title="Red">
|
||||
<label for="rValue">R</label>
|
||||
<input type="number" id="rValue" min="0" max="255" step="1">
|
||||
</div>
|
||||
<div class="inline-field value-field" title="Green">
|
||||
<label for="gValue">G</label>
|
||||
<input type="number" id="gValue" min="0" max="255" step="1">
|
||||
</div>
|
||||
<div class="inline-field value-field" title="Blue">
|
||||
<label for="bValue">B</label>
|
||||
<input type="number" id="bValue" min="0" max="255" step="1">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-container">
|
||||
<button id="originalPreview" type="button" class="color-preview original-preview" title="Original color" data-i18n-title="Original color">
|
||||
<svg viewBox="0 0 24 24" width="24" height="24">
|
||||
<circle cx="11.5" cy="11.5" r="11.5" style="fill:#ff6f00;"/>
|
||||
<g style="fill:none;stroke:#fff;stroke-linecap:round;stroke-linejoin:round;">
|
||||
<polyline points="5.5 5.5 5.5 9.5 9.5 9.5" />
|
||||
<path d="M5.84,9.5c.82-2.33,3.05-4,5.66-4,3.31,0,6,2.69,6,6s-2.69,6-6,6c-2.95,0-5.41-2.13-5.91-4.94"/>
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="colorPreview" class="color-preview" title="Save current color" data-i18n-title="Save current color">
|
||||
<svg viewBox="0 0 24 24" width="24" height="24">
|
||||
<circle cx="11.5" cy="11.5" r="11.5" style="fill:#009688"/>
|
||||
<path d="M11.85,5.11l1.43,4.39h4.61c.35,0,.5.45.22.66l-3.73,2.71,1.43,4.39c.11.34-.28.62-.56.41l-3.73-2.71-3.73,2.71c-.29.21-.67-.07-.56-.41l1.43-4.39-3.73-2.71c-.29-.21-.14-.66.22-.66h4.61s1.43-4.39,1.43-4.39c.11-.34.59-.34.7,0Z" style="fill:none; stroke:#fff; stroke-linecap:round; stroke-linejoin:round;"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="saved-grid" id="userColors"></div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button type="button" id="paletteToggle" class="icon-button" aria-expanded="false" aria-controls="palettePanel" title="Palettes" data-i18n-title="Palettes">
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><rect x="1.5" y="2.5" width="13" height="11" rx="2" fill="none" stroke="currentColor"/><path d="M5.5 2.5v11" stroke="currentColor"/></svg>
|
||||
</button>
|
||||
<div class="stretch-spacer"></div>
|
||||
<button id="confirmBtn" class="ButtonTypeChoice ButtonStyleConfirm" data-i18n="OK">OK</button>
|
||||
<button id="cancelBtn" class="ButtonTypeChoice ButtonStyleRegular" data-i18n="Cancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<script src="palettes.js"></script><script src="colorpicker-model.js"></script><script src="colorpicker.js"></script></body></html>
|
||||
@@ -1,459 +0,0 @@
|
||||
"use strict";
|
||||
(() => {
|
||||
const cssColors = [
|
||||
{hex: "#FF0000", name: "Red"},
|
||||
{hex: "#DC143C", name: "Crimson"},
|
||||
{hex: "#FF4500", name: "Orange red"},
|
||||
{hex: "#FF6347", name: "Tomato"},
|
||||
{hex: "#FFA500", name: "Orange"},
|
||||
{hex: "#FF8C00", name: "Dark orange"},
|
||||
{hex: "#FFD700", name: "Gold"},
|
||||
{hex: "#DEB887", name: "Burly wood"},
|
||||
{hex: "#FFE4C4", name: "Bisque"},
|
||||
{hex: "#FFE4E1", name: "Misty rose"},
|
||||
{hex: "#FFFF00", name: "Yellow"},
|
||||
{hex: "#FFFFE0", name: "Light yellow"},
|
||||
{hex: "#FFFACD", name: "Lemon chiffon"},
|
||||
{hex: "#F5F5DC", name: "Beige"},
|
||||
{hex: "#FAFAD2", name: "Light golden rod yellow"},
|
||||
{hex: "#EEE8AA", name: "Pale golden rod"},
|
||||
{hex: "#F0E68C", name: "Khaki"},
|
||||
{hex: "#BDB76B", name: "Dark khaki"},
|
||||
{hex: "#FFDAB9", name: "Peach puff"},
|
||||
{hex: "#FFE4B5", name: "Moccasin"},
|
||||
{hex: "#FFEFD5", name: "Papaya whip"},
|
||||
{hex: "#FFEBCD", name: "Blanched almond"},
|
||||
{hex: "#FAEBD7", name: "Antique white"},
|
||||
{hex: "#FFDEAD", name: "Navajo white"},
|
||||
{hex: "#F5DEB3", name: "Wheat"},
|
||||
{hex: "#DAA520", name: "Golden rod"},
|
||||
{hex: "#D2691E", name: "Chocolate"},
|
||||
{hex: "#CD853F", name: "Peru"},
|
||||
{hex: "#B22222", name: "Fire brick"},
|
||||
{hex: "#A0522D", name: "Sienna"},
|
||||
{hex: "#8B4513", name: "Saddle brown"},
|
||||
{hex: "#A52A2A", name: "Brown"},
|
||||
{hex: "#BC8F8F", name: "Rosy brown"},
|
||||
{hex: "#FA8072", name: "Salmon"},
|
||||
{hex: "#FFA07A", name: "Light salmon"},
|
||||
{hex: "#E9967A", name: "Dark salmon"},
|
||||
{hex: "#F4A460", name: "Sandy brown"},
|
||||
{hex: "#FF7F50", name: "Coral"},
|
||||
{hex: "#F08080", name: "Light coral"},
|
||||
{hex: "#CD5C5C", name: "Indian red"},
|
||||
{hex: "#8B0000", name: "Dark red"},
|
||||
{hex: "#800000", name: "Maroon"},
|
||||
{hex: "#ADFF2F", name: "Green yellow"},
|
||||
{hex: "#9ACD32", name: "Yellow green"},
|
||||
{hex: "#7FFF00", name: "Chartreuse"},
|
||||
{hex: "#7CFC00", name: "Lawn green"},
|
||||
{hex: "#32CD32", name: "Lime green"},
|
||||
{hex: "#00FF7F", name: "Spring green"},
|
||||
{hex: "#00FA9A", name: "Medium spring green"},
|
||||
{hex: "#00FF00", name: "Lime"},
|
||||
{hex: "#008000", name: "Green"},
|
||||
{hex: "#228B22", name: "Forest green"},
|
||||
{hex: "#006400", name: "Dark green"},
|
||||
{hex: "#6B8E23", name: "Olive drab"},
|
||||
{hex: "#808000", name: "Olive"},
|
||||
{hex: "#556B2F", name: "Dark olive green"},
|
||||
{hex: "#98FB98", name: "Pale green"},
|
||||
{hex: "#90EE90", name: "Light green"},
|
||||
{hex: "#3CB371", name: "Medium sea green"},
|
||||
{hex: "#2E8B57", name: "Sea green"},
|
||||
{hex: "#8FBC8F", name: "Dark sea green"},
|
||||
{hex: "#5F9EA0", name: "Cadet blue"},
|
||||
{hex: "#66CDAA", name: "Medium aqua marine"},
|
||||
{hex: "#40E0D0", name: "Turquoise"},
|
||||
{hex: "#48D1CC", name: "Medium turquoise"},
|
||||
{hex: "#00CED1", name: "Dark turquoise"},
|
||||
{hex: "#AFEEEE", name: "Pale turquoise"},
|
||||
{hex: "#00FFFF", name: "Aqua"},
|
||||
{hex: "#00FFFF", name: "Cyan"},
|
||||
{hex: "#E0FFFF", name: "Light cyan"},
|
||||
{hex: "#F5FFFA", name: "Mint cream"},
|
||||
{hex: "#F0FFF0", name: "Honey dew"},
|
||||
{hex: "#7FFFD4", name: "Aquamarine"},
|
||||
{hex: "#20B2AA", name: "Light sea green"},
|
||||
{hex: "#008B8B", name: "Dark cyan"},
|
||||
{hex: "#008080", name: "Teal"},
|
||||
{hex: "#2F4F4F", name: "Dark slate gray"},
|
||||
{hex: "#778899", name: "Light slate gray"},
|
||||
{hex: "#708090", name: "Slate gray"},
|
||||
{hex: "#00BFFF", name: "Deep sky blue"},
|
||||
{hex: "#1E90FF", name: "Dodger Blue"},
|
||||
{hex: "#87CEEB", name: "Sky blue"},
|
||||
{hex: "#87CEFA", name: "Light sky blue"},
|
||||
{hex: "#ADD8E6", name: "Light blue"},
|
||||
{hex: "#B0E0E6", name: "Powder blue"},
|
||||
{hex: "#B0C4DE", name: "Light steel blue"},
|
||||
{hex: "#4682B4", name: "Steel blue"},
|
||||
{hex: "#6495ED", name: "Cornflower blue"},
|
||||
{hex: "#4169E1", name: "Royal blue"},
|
||||
{hex: "#0000FF", name: "Blue"},
|
||||
{hex: "#0000CD", name: "Medium blue"},
|
||||
{hex: "#191970", name: "Midnight blue"},
|
||||
{hex: "#00008B", name: "Dark blue"},
|
||||
{hex: "#000080", name: "Navy"},
|
||||
{hex: "#6A5ACD", name: "Slate blue"},
|
||||
{hex: "#483D8B", name: "Dark slate blue"},
|
||||
{hex: "#7B68EE", name: "Medium slate blue"},
|
||||
{hex: "#8A2BE2", name: "Blue violet"},
|
||||
{hex: "#4B0082", name: "Indigo"},
|
||||
{hex: "#9400D3", name: "Dark violet"},
|
||||
{hex: "#9932CC", name: "Dark orchid"},
|
||||
{hex: "#BA55D3", name: "Medium orchid"},
|
||||
{hex: "#DA70D6", name: "Orchid"},
|
||||
{hex: "#EE82EE", name: "Violet"},
|
||||
{hex: "#9370DB", name: "Medium purple"},
|
||||
{hex: "#800080", name: "Purple"},
|
||||
{hex: "#663399", name: "Rebecca purple"},
|
||||
{hex: "#C71585", name: "Medium violet red"},
|
||||
{hex: "#DB7093", name: "PaleV violet red"},
|
||||
{hex: "#FF1493", name: "Deep pink"},
|
||||
{hex: "#FF69B4", name: "Hot pink"},
|
||||
{hex: "#FFB6C1", name: "Light pink"},
|
||||
{hex: "#FFC0CB", name: "Pink"},
|
||||
{hex: "#DDA0DD", name: "Plum"},
|
||||
{hex: "#D8BFD8", name: "Thistle"},
|
||||
{hex: "#FF00FF", name: "Magenta"},
|
||||
{hex: "#FF00FF", name: "Fuchsia"},
|
||||
{hex: "#8B008B", name: "Dark magenta"},
|
||||
|
||||
{hex: "#FAF0E6", name: "Linen"},
|
||||
{hex: "#FDF5E6", name: "Old lace"},
|
||||
{hex: "#FFF5EE", name: "Sea shell"},
|
||||
{hex: "#FFFFF0", name: "Ivory"},
|
||||
{hex: "#FFFAF0", name: "Floral white"},
|
||||
{hex: "#FFF0F5", name: "Lavender blush"},
|
||||
{hex: "#F8F8FF", name: "Ghost white"},
|
||||
{hex: "#F0F8FF", name: "Alice blue"},
|
||||
{hex: "#E6E6FA", name: "Lavender"},
|
||||
{hex: "#F5F5F5", name: "White smoke"},
|
||||
{hex: "#DCDCDC", name: "Gainsboro"},
|
||||
];
|
||||
|
||||
const palettes = {
|
||||
basic: [
|
||||
{hex: "#FF0000", name: "Red"},
|
||||
{hex: "#FF8000", name: "Orange"},
|
||||
{hex: "#FFFF00", name: "Yellow"},
|
||||
{hex: "#80FF00", name: "Lime"},
|
||||
{hex: "#00FF00", name: "Green"},
|
||||
{hex: "#00FFFF", name: "Cyan"},
|
||||
{hex: "#0000FF", name: "Blue"},
|
||||
{hex: "#8000FF", name: "Purple"},
|
||||
{hex: "#FF00FF", name: "Magenta"},
|
||||
{hex: "#FF8080", name: "Pink"},
|
||||
{hex: "#804000", name: "Brown"},
|
||||
{hex: "#000000", name: "Black"},
|
||||
{hex: "#191919", name: "%90"},
|
||||
{hex: "#333333", name: "%80"},
|
||||
{hex: "#4c4c4c", name: "%70"},
|
||||
{hex: "#666666", name: "%60"},
|
||||
{hex: "#808080", name: "%50"},
|
||||
{hex: "#999999", name: "%40"},
|
||||
{hex: "#b3b3b3", name: "%30"},
|
||||
{hex: "#cccccc", name: "%20"},
|
||||
{hex: "#e6e6e6", name: "%10"},
|
||||
{hex: "#FFFFFF", name: "White"},
|
||||
...cssColors
|
||||
],
|
||||
orca: [
|
||||
{hex: "#ED1C24", name: "Coral fire"},
|
||||
{hex: "#C41E3A", name: "Sea anemone"},
|
||||
{hex: "#800000", name: "Deep abyss"},
|
||||
{hex: "#B22222", name: "Lobster red"},
|
||||
{hex: "#A0522D", name: "Sandy clay"},
|
||||
{hex: "#8B4513", name: "Driftwood"},
|
||||
{hex: "#D2691E", name: "Sea buckthorn"},
|
||||
{hex: "#F26722", name: "Sunset reef"},
|
||||
{hex: "#FF8C00", name: "Tiger coral"},
|
||||
{hex: "#FA8173", name: "Pink coral"},
|
||||
{hex: "#FFA07A", name: "Seashell glow"},
|
||||
{hex: "#F4E2C1", name: "Sand"},
|
||||
{hex: "#FFD700", name: "Golden kelp"},
|
||||
{hex: "#F7B763", name: "Sunlit dune"},
|
||||
{hex: "#FFAA33", name: "Amber nautilus"},
|
||||
{hex: "#FFEB31", name: "Sea lemon"},
|
||||
{hex: "#D4ED91", name: "Pale starfish"},
|
||||
{hex: "#00FF7F", name: "Spring tide"},
|
||||
{hex: "#32CD32", name: "Lime algae"},
|
||||
{hex: "#A4C41E", name: "Sea grass"},
|
||||
{hex: "#7CFC00", name: "Neon plankton"},
|
||||
{hex: "#228B22", name: "Kelp forest"},
|
||||
{hex: "#008000", name: "Emerald wave"},
|
||||
{hex: "#345B2F", name: "Whale shadow"},
|
||||
{hex: "#2E8B57", name: "Sea turtle"},
|
||||
{hex: "#00C1AE", name: "Lagoon"},
|
||||
{hex: "#20B2AA", name: "Tropical current"},
|
||||
{hex: "#115877", name: "Midnight trench"},
|
||||
{hex: "#008B8B", name: "Dark kelp"},
|
||||
{hex: "#00FFFF", name: "Crystal surf"},
|
||||
{hex: "#40E0D0", name: "Turquoise bay"},
|
||||
{hex: "#2EBDEF", name: "Sky over reef"},
|
||||
{hex: "#87CEEB", name: "Shallow sky"},
|
||||
{hex: "#1E90FF", name: "Electric bel"},
|
||||
{hex: "#4169E1", name: "Dolphin"},
|
||||
{hex: "#0000CD", name: "Ocean depths"},
|
||||
{hex: "#191970", name: "Whale"},
|
||||
{hex: "#4B0082", name: "Indigo abyss"},
|
||||
{hex: "#7841CE", name: "Sea urchin"},
|
||||
{hex: "#9370DB", name: "Lavender coral"},
|
||||
{hex: "#800080", name: "Violet tide"},
|
||||
{hex: "#8B008B", name: "Deep sea orchid"},
|
||||
{hex: "#FF00FF", name: "Neon jelly"},
|
||||
{hex: "#ED1E79", name: "Fuchsia coral"},
|
||||
{hex: "#FF69B4", name: "Pink jellyfish"},
|
||||
{hex: "#FFFFFF", name: "Foam"},
|
||||
{hex: "#D3D3D3", name: "Seashell white"},
|
||||
{hex: "#808080", name: "Pebble gray"},
|
||||
{hex: "#696969", name: "Storm cloud"},
|
||||
{hex: "#000000", name: "Ink black"}
|
||||
],
|
||||
ral: [
|
||||
{hex: "#716F52", name: "1000 Green beige"},
|
||||
{hex: "#9B8E79", name: "1001 Beige"},
|
||||
{hex: "#D4BB87", name: "1002 Sand yellow"},
|
||||
{hex: "#F4A460", name: "1003 Signal yellow"},
|
||||
{hex: "#EFBF04", name: "1004 Golden yellow"},
|
||||
{hex: "#D7A500", name: "1005 Honey yellow"},
|
||||
{hex: "#F2D185", name: "1006 Maize yellow"},
|
||||
{hex: "#FDD185", name: "1007 Dalmatian yellow"},
|
||||
{hex: "#8B7D6B", name: "1011 Brown beige"},
|
||||
{hex: "#F5DB50", name: "1012 Lemon yellow"},
|
||||
{hex: "#F3E5AB", name: "1013 Oyster white"},
|
||||
{hex: "#F8E4BC", name: "1014 Ivory"},
|
||||
{hex: "#FAE6D3", name: "1015 Light ivory"},
|
||||
{hex: "#E4B922", name: "1018 Zinc yellow"},
|
||||
{hex: "#A19D94", name: "1019 Grey beige"},
|
||||
{hex: "#B9975B", name: "1020 Olive yellow"},
|
||||
{hex: "#B8860B", name: "1021 Rape yellow"},
|
||||
{hex: "#FF8C00", name: "1023 Traffic yellow"},
|
||||
{hex: "#CC7722", name: "1024 Ochre yellow"},
|
||||
{hex: "#FDBA37", name: "1026 Traffic orange yellow"},
|
||||
{hex: "#F3A55C", name: "1027 Curry"},
|
||||
{hex: "#E87C33", name: "1028 Melon yellow"},
|
||||
{hex: "#F2A641", name: "1032 Broom yellow"},
|
||||
{hex: "#E09F39", name: "1033 Dahlia yellow"},
|
||||
{hex: "#F0B76E", name: "1034 Pastel yellow"},
|
||||
{hex: "#D1BCA0", name: "1035 Pearl beige"},
|
||||
{hex: "#D4AF37", name: "1036 Pearl gold"},
|
||||
{hex: "#F7C64F", name: "1037 Sun yellow"},
|
||||
{hex: "#F28C38", name: "2000 Yellow orange"},
|
||||
{hex: "#E55B2B", name: "2001 Deep orange"},
|
||||
{hex: "#E34234", name: "2002 Vermilion"},
|
||||
{hex: "#F27B33", name: "2003 Pearl orange"},
|
||||
{hex: "#F37322", name: "2004 Pure orange"},
|
||||
{hex: "#F75C03", name: "2005 Luminous orange"},
|
||||
{hex: "#F46C1F", name: "2007 Luminous bright orange"},
|
||||
{hex: "#E14423", name: "2008 Bright red orange"},
|
||||
{hex: "#F28500", name: "2009 Traffic orange"},
|
||||
{hex: "#F46A0A", name: "2010 Signal orange"},
|
||||
{hex: "#E0490C", name: "2011 Deep orange"},
|
||||
{hex: "#F46F42", name: "2012 Salmon orange"},
|
||||
{hex: "#E6761C", name: "2013 Pearl orange"},
|
||||
{hex: "#E8A18B", name: "2015 Pearl pink"},
|
||||
{hex: "#B8612C", name: "2016 Copper orange"},
|
||||
{hex: "#8B4513", name: "2017 Signal brown"},
|
||||
{hex: "#C14A0F", name: "2018 Fir orange"},
|
||||
{hex: "#BC2E1F", name: "2020 Signal red"},
|
||||
{hex: "#D52713", name: "2021 Luminous red"},
|
||||
{hex: "#9F261E", name: "2022 Carmine red"},
|
||||
{hex: "#D53832", name: "2023 Pearl carmine red"},
|
||||
{hex: "#C2341C", name: "2024 Pure red"},
|
||||
{hex: "#B32D1C", name: "2025 Luminous deep red"},
|
||||
{hex: "#A62320", name: "2027 Currant red"},
|
||||
{hex: "#B9301E", name: "2028 Pearl ruby red"},
|
||||
{hex: "#A91D2E", name: "3000 Fire red"},
|
||||
{hex: "#A1262A", name: "3001 Signal red"},
|
||||
{hex: "#8F2626", name: "3002 Carmine red"},
|
||||
{hex: "#9B2428", name: "3003 Ruby red"},
|
||||
{hex: "#872241", name: "3004 Purple red"},
|
||||
{hex: "#722F37", name: "3005 Wine red"},
|
||||
{hex: "#3D1818", name: "3007 Black red"},
|
||||
{hex: "#8A3324", name: "3009 Oxide red"},
|
||||
{hex: "#A22C29", name: "3011 Brown red"},
|
||||
{hex: "#B22D20", name: "3012 Pine red"},
|
||||
{hex: "#D23741", name: "3013 Tomato red"},
|
||||
{hex: "#A8282E", name: "3014 Antique pink"},
|
||||
{hex: "#C8283C", name: "3015 Light pink"},
|
||||
{hex: "#D23F39", name: "3016 Coral red"},
|
||||
{hex: "#D1425E", name: "3017 Rose"},
|
||||
{hex: "#C4232F", name: "3018 Strawberry red"},
|
||||
{hex: "#C8102E", name: "3020 Traffic red"},
|
||||
{hex: "#E36A5A", name: "3022 Salmon pink"},
|
||||
{hex: "#E63E62", name: "3024 Luminous rose"},
|
||||
{hex: "#E02A27", name: "3026 Luminous bright red"},
|
||||
{hex: "#B8315F", name: "3027 Raspberry red"},
|
||||
{hex: "#C61C23", name: "3028 Pure red"},
|
||||
{hex: "#AD2932", name: "3031 Orient red"},
|
||||
{hex: "#D32F2F", name: "3032 Pearl ruby"},
|
||||
{hex: "#D13C6F", name: "3033 Pearl pink"},
|
||||
{hex: "#7B2B5F", name: "4001 Red purple"},
|
||||
{hex: "#912E8C", name: "4002 Red violet"},
|
||||
{hex: "#9C366F", name: "4003 Heather violet"},
|
||||
{hex: "#6A1F49", name: "4004 Bordeaux violet"},
|
||||
{hex: "#4B2663", name: "4005 Blue violet"},
|
||||
{hex: "#5C2D3C", name: "4006 Mahogany"},
|
||||
{hex: "#5E3559", name: "4007 Grey purple"},
|
||||
{hex: "#9C3568", name: "4008 Signal violet"},
|
||||
{hex: "#5E6E3A", name: "4009 Fir green"},
|
||||
{hex: "#5B5B8A", name: "4010 Sabine grey"},
|
||||
{hex: "#8F3A70", name: "4011 Pearl violet"},
|
||||
{hex: "#5E3252", name: "4012 Pearl blackberry"},
|
||||
{hex: "#1B4F72", name: "5001 Green blue"},
|
||||
{hex: "#18669E", name: "5002 Ultramarine blue"},
|
||||
{hex: "#0F4C8A", name: "5003 Sapphire blue"},
|
||||
{hex: "#0D324A", name: "5004 Black blue"},
|
||||
{hex: "#1B5583", name: "5005 Signal blue"},
|
||||
{hex: "#2C5F9D", name: "5007 Brilliant blue"},
|
||||
{hex: "#3B5AA9", name: "5008 Grey blue"},
|
||||
{hex: "#0077A3", name: "5009 Azure blue"},
|
||||
{hex: "#0A558C", name: "5010 Gentian blue"},
|
||||
{hex: "#2B547E", name: "5011 Steel blue"},
|
||||
{hex: "#0066A1", name: "5012 Light blue"},
|
||||
{hex: "#124780", name: "5013 Cobalt blue"},
|
||||
{hex: "#1F4E79", name: "5014 Pigeon blue"},
|
||||
{hex: "#0384A7", name: "5015 Sky blue"},
|
||||
{hex: "#006A9E", name: "5017 Traffic blue"},
|
||||
{hex: "#005F87", name: "5018 Turquoise blue"},
|
||||
{hex: "#5A3D5B", name: "5019 Capsanthin violet"},
|
||||
{hex: "#04749F", name: "5020 Ocean blue"},
|
||||
{hex: "#009AAA", name: "5021 Water blue"},
|
||||
{hex: "#1A3D5F", name: "5022 Night blue"},
|
||||
{hex: "#047AA6", name: "5023 Distant blue"},
|
||||
{hex: "#5B9BB8", name: "5024 Pastel blue"},
|
||||
{hex: "#3C6A98", name: "5025 Pearl gentian blue"},
|
||||
{hex: "#1C3F5F", name: "5026 Pearl midnight blue"},
|
||||
{hex: "#36673A", name: "6000 Patina green"},
|
||||
{hex: "#0E5C34", name: "6001 Emerald green"},
|
||||
{hex: "#437034", name: "6002 Leaf green"},
|
||||
{hex: "#3F3F2A", name: "6003 Olive green"},
|
||||
{hex: "#2B4E52", name: "6004 Blue green"},
|
||||
{hex: "#3B5F3F", name: "6005 Moss green"},
|
||||
{hex: "#4A563C", name: "6006 Grey olive"},
|
||||
{hex: "#2B4638", name: "6007 Pine green"},
|
||||
{hex: "#4A4139", name: "6008 Pine brown"},
|
||||
{hex: "#3B3A2E", name: "6009 Fir green"},
|
||||
{hex: "#3E7A5F", name: "6010 Grass green"},
|
||||
{hex: "#5E716A", name: "6011 Reseda green"},
|
||||
{hex: "#3A4E48", name: "6012 Brown green"},
|
||||
{hex: "#6B8E23", name: "6013 Reed green"},
|
||||
{hex: "#6B8E23", name: "6014 Yellow green"},
|
||||
{hex: "#1E3E2F", name: "6015 Black green"},
|
||||
{hex: "#1E4D4B", name: "6016 Turquoise green"},
|
||||
{hex: "#4C7C47", name: "6017 May green"},
|
||||
{hex: "#9CBF60", name: "6018 Yellow green"},
|
||||
{hex: "#2E5A3A", name: "6019 Pine green"},
|
||||
{hex: "#2E5A3A", name: "6020 Chrome oxide green"},
|
||||
{hex: "#9DC0A5", name: "6021 Pale green"},
|
||||
{hex: "#4A563C", name: "6022 Brown green"},
|
||||
{hex: "#009E60", name: "6024 Traffic green"},
|
||||
{hex: "#3F7044", name: "6025 Fern green"},
|
||||
{hex: "#4F7942", name: "6026 Opal green"},
|
||||
{hex: "#3B5F3F", name: "6027 Pine green"},
|
||||
{hex: "#3B5F3F", name: "6028 Pine green"},
|
||||
{hex: "#00A550", name: "6029 Mint green"},
|
||||
{hex: "#3E5A40", name: "6031 Bronze green"},
|
||||
{hex: "#3D7E5A", name: "6032 Pearl green"},
|
||||
{hex: "#007D6E", name: "6033 Sax turquoise"},
|
||||
{hex: "#3D7E5A", name: "6034 Pearl turquoise"},
|
||||
{hex: "#3D7E5A", name: "6035 Pearl green"},
|
||||
{hex: "#3D7E5A", name: "6036 Pearl jade"},
|
||||
{hex: "#00A550", name: "6037 Pure green"},
|
||||
{hex: "#00A550", name: "6038 Luminous green"},
|
||||
{hex: "#00A550", name: "6039 Mint green"},
|
||||
{hex: "#7D7D7D", name: "7000 Squirrel grey"},
|
||||
{hex: "#A8A9AD", name: "7001 Silver grey"},
|
||||
{hex: "#6F7368", name: "7002 Olivestone"},
|
||||
{hex: "#8F9080", name: "7003 Moss grey"},
|
||||
{hex: "#8E9291", name: "7004 Signal grey"},
|
||||
{hex: "#8B8788", name: "7005 Mouse grey"},
|
||||
{hex: "#9A8373", name: "7006 Beige grey"},
|
||||
{hex: "#828C6A", name: "7008 Khaki grey"},
|
||||
{hex: "#6F7368", name: "7009 Green grey"},
|
||||
{hex: "#5F6563", name: "7010 Tarpaulin grey"},
|
||||
{hex: "#4A5656", name: "7011 Iron grey"},
|
||||
{hex: "#4D5656", name: "7012 Basalt grey"},
|
||||
{hex: "#635147", name: "7013 Brown grey"},
|
||||
{hex: "#6B6B83", name: "7015 Slate grey"},
|
||||
{hex: "#32373D", name: "7016 Anthracite grey"},
|
||||
{hex: "#8B8B83", name: "7021 Light grey"},
|
||||
{hex: "#716347", name: "7022 Umber grey"},
|
||||
{hex: "#76787A", name: "7023 Concrete grey"},
|
||||
{hex: "#5E6565", name: "7024 Graphite grey"},
|
||||
{hex: "#5E6565", name: "7026 Granite grey"},
|
||||
{hex: "#8F9494", name: "7030 Stone grey"},
|
||||
{hex: "#4A5656", name: "7031 Iron grey"},
|
||||
{hex: "#B2BFC0", name: "7032 Pebble grey"},
|
||||
{hex: "#9B9B9B", name: "7033 Cement grey"},
|
||||
{hex: "#8F9D9B", name: "7034 Yellow grey"},
|
||||
{hex: "#CBD0CC", name: "7035 Light grey"},
|
||||
{hex: "#6D7A80", name: "7036 Platinum grey"},
|
||||
{hex: "#8A9191", name: "7037 Dusty grey"},
|
||||
{hex: "#7E8181", name: "7038 Agate grey"},
|
||||
{hex: "#6E7D7E", name: "7039 Quartz grey"},
|
||||
{hex: "#8A9191", name: "7040 Window grey"},
|
||||
{hex: "#8A9191", name: "7042 Traffic grey A"},
|
||||
{hex: "#6E7D7E", name: "7043 Traffic grey B"},
|
||||
{hex: "#9B9B9B", name: "7044 Silk grey"},
|
||||
{hex: "#A1A7B0", name: "7045 Telegrey 1"},
|
||||
{hex: "#7E848C", name: "7046 Telegrey 2"},
|
||||
{hex: "#7E848C", name: "7047 Telegrey 4"},
|
||||
{hex: "#6D7A80", name: "7048 Pearl mouse grey"},
|
||||
{hex: "#786D5F", name: "8000 Green brown"},
|
||||
{hex: "#8B7355", name: "8001 Ochre brown"},
|
||||
{hex: "#8B6F47", name: "8003 Clay brown"},
|
||||
{hex: "#8B6F47", name: "8004 Copper brown"},
|
||||
{hex: "#5C4033", name: "8007 Chocolate brown"},
|
||||
{hex: "#6E5C4A", name: "8008 Olive brown"},
|
||||
{hex: "#8B4513", name: "8011 Nut brown"},
|
||||
{hex: "#8B4513", name: "8012 Red brown"},
|
||||
{hex: "#5C3317", name: "8014 Sepia brown"},
|
||||
{hex: "#854636", name: "8015 Chestnut brown"},
|
||||
{hex: "#8B4513", name: "8016 Mahogany brown"},
|
||||
{hex: "#5C3317", name: "8017 Chocolate brown"},
|
||||
{hex: "#786D5F", name: "8019 Grey brown"},
|
||||
{hex: "#854636", name: "8022 Chestnut brown"},
|
||||
{hex: "#8B6F47", name: "8023 Pearl copper"},
|
||||
{hex: "#8B7355", name: "8024 Beige brown"},
|
||||
{hex: "#8B6F47", name: "8025 Pearl mocha brown"},
|
||||
{hex: "#8B6F47", name: "8027 Clay brown"},
|
||||
{hex: "#8B6F47", name: "8028 Earth brown"},
|
||||
{hex: "#8B6F47", name: "8029 Pearl brown"},
|
||||
{hex: "#FAF0E6", name: "9001 Cream"},
|
||||
{hex: "#EDECE6", name: "9002 Grey white"},
|
||||
{hex: "#FFFFFF", name: "9003 Signal white"},
|
||||
{hex: "#000000", name: "9004 Signal black"},
|
||||
{hex: "#1D1D1D", name: "9005 Jet black"},
|
||||
{hex: "#C0C0C0", name: "9006 White aluminium"},
|
||||
{hex: "#8C7853", name: "9007 Grey aluminium"},
|
||||
{hex: "#FFFFFF", name: "9010 Pure white"},
|
||||
{hex: "#1D1D1D", name: "9011 Graphite black"},
|
||||
{hex: "#FFFFFF", name: "9016 Traffic white"},
|
||||
{hex: "#000000", name: "9017 Traffic black"},
|
||||
{hex: "#FAF0E6", name: "9018 Papyrus white"},
|
||||
{hex: "#B2BFC0", name: "9022 Pearl light grey"}
|
||||
],
|
||||
gradient: [
|
||||
{grad: "#FFF47B-#CD9800", name: "Metal: Gold"},
|
||||
{grad: "#373737-#F5F4F4", name: "Metal: Silver"},
|
||||
{grad: "#C39292-#C73725", name: "Metal: Copper"},
|
||||
{grad: "#DED699-#C99620", name: "Metal: Brass"},
|
||||
{grad: "#6E4316-#B0742E", name: "Metal: Bronze"},
|
||||
// More than two colors: only listed when the page enables multi-color gradients.
|
||||
{grad: "#FF0000-#FFFF00-#00FF00-#00FFFF-#0000FF-#FF00FF-#FF0000", name: "Nature: Rainbow"},
|
||||
|
||||
{grad: "#D8FEE7-#FFF5BD", name: "Nature: Pearl"},
|
||||
{grad: "#1E5AA8-#3D8BD9-#7DB9F0-#C9E6FF", name: "Nature: Clear Sky"},
|
||||
{grad: "#2B1B5A-#7A2E7E-#D9466E-#F57C3A-#FFC857", name: "Nature: Sunset"},
|
||||
{grad: "#3B2416-#5A3A24-#45291A-#6B4630-#3B2416", name: "Wood: Dark"},
|
||||
{grad: "#8B5A2B-#A8733F-#946235-#B5844F-#8B5A2B", name: "Wood: Medium"},
|
||||
{grad: "#D9B98C-#E8CFA6-#D3AE7C-#EBD5B0-#D9B98C", name: "Wood: Light"},
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
window.ColorPickerPalettes = palettes;
|
||||
})();
|
||||
@@ -1,534 +0,0 @@
|
||||
:root {
|
||||
--thumb-size : 20px;
|
||||
--field-height: 28px;
|
||||
--number-width: 52px;
|
||||
--preview-size: 52px;
|
||||
--checkerboard: conic-gradient(var(--checker-light) 25%, var(--checker-dark) 0 50%, var(--checker-light) 0 75%, var(--checker-dark) 0);
|
||||
}
|
||||
|
||||
* {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
/* Report the full content width even before the native host expands its viewport.
|
||||
WebKit's fit-content can clip the sidebar and retain the expanded width on close. */
|
||||
width: max-content;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--orca-font, system-ui, sans-serif);
|
||||
background: var(--bg-color);
|
||||
color: var(--fg-color-text);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#selectionNotice {
|
||||
width: 0;
|
||||
min-width: 100%;
|
||||
margin-bottom: 8px;
|
||||
color: var(--fg-color-label);
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
height: 360px;
|
||||
}
|
||||
|
||||
/* ---------- Palette sidebar ---------- */
|
||||
.left-panel {
|
||||
flex: 0 0 160px;
|
||||
width: 160px;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
.left-panel.open {
|
||||
display: flex;
|
||||
padding-right: 0px;
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
#paletteCombo {
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Combo box or filter box, with the search / close button on the right. */
|
||||
.palette-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
height: 25px;
|
||||
margin-right:5px;
|
||||
}
|
||||
.palette-toolbar > .palette-select { flex: 1 }
|
||||
.palette-filter {
|
||||
flex: 1;
|
||||
height: 25px;
|
||||
padding: 0 6px;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
color: var(--fg-color-text);
|
||||
background: var(--bg-color);
|
||||
border: 1px solid var(--main-color);
|
||||
outline: none;
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
.palette-filter::-webkit-search-cancel-button { -webkit-appearance: none }
|
||||
.palette-filter:focus-visible{
|
||||
background-color: var(--focus-bg-box);
|
||||
}
|
||||
|
||||
.palette-search-button {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background-color: var(--button-bg-normal);
|
||||
border: 1px solid var(--border-color);
|
||||
border-left: 0;
|
||||
}
|
||||
.palette-search-button:hover {
|
||||
background-color: var(--button-bg-hover);
|
||||
}
|
||||
|
||||
.palette-search-button .icon-search {
|
||||
--icon-url: var(--icon-search);
|
||||
}
|
||||
.palette-search-button .icon-close {
|
||||
--icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><g style="fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round"><line x1="3.5" y1="2.5" x2="13.5" y2="12.5"/><line x1="3.5" y1="12.5" x2="13.5" y2="2.5"/></g></svg>');;
|
||||
}
|
||||
.palette-search-button .icon-close,
|
||||
.palette-search-button[aria-pressed="true"] .icon-search { display: none }
|
||||
.palette-search-button[aria-pressed="true"] .icon-close { display: block }
|
||||
.palette-empty {
|
||||
flex: 1;
|
||||
padding: 8px 2px;
|
||||
font-size: 13px;
|
||||
color: var(--fg-color-label);
|
||||
}
|
||||
|
||||
.palette {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-right: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.color-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1px 2px 1px 0;
|
||||
border:1px solid transparent;
|
||||
font-size: 13px;
|
||||
color: var(--fg-color-text);
|
||||
}
|
||||
|
||||
.color-item:hover {
|
||||
border-color: var(--main-color);
|
||||
}
|
||||
|
||||
.color-swatch {
|
||||
flex: 0 0 18px;
|
||||
height: 18px;
|
||||
border-radius: 3px;
|
||||
margin-right: 5px;
|
||||
margin: 0 5px 0 1px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.color-name {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* ---------- Picker ---------- */
|
||||
.right-panel {
|
||||
flex: 0 0 330px;
|
||||
/* WebKit uses width, rather than flex-basis, for the parent's intrinsic width. */
|
||||
width: 330px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.spectrum-row {
|
||||
--spectrum-height: 150px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
height: var(--spectrum-height);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.spectrum-container {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
border-radius: 7px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#spectrum {
|
||||
position: absolute; /* Absolute so the canvas's pixel size never feeds back into the flex layout */
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
#pickerEditors:disabled #spectrum { pointer-events: none }
|
||||
|
||||
#colorIndicator {
|
||||
position: absolute;
|
||||
width: var(--thumb-size);
|
||||
height: var(--thumb-size);
|
||||
border: 2px solid var(--color-marker-outline);
|
||||
box-shadow: 0 0 0 2px var(--color-marker-shadow);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.saturation-slider {
|
||||
position: relative;
|
||||
flex: 0 0 var(--thumb-size);
|
||||
}
|
||||
|
||||
#satSlider {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
flex: none;
|
||||
width: var(--spectrum-height);
|
||||
transform: translate(-50%, -50%) rotate(-90deg);
|
||||
}
|
||||
|
||||
.valueSlider {
|
||||
height: 14px;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.valueSlider::-webkit-slider-runnable-track {
|
||||
height: 14px;
|
||||
border-radius: 8px;
|
||||
background: var(--track-gradient);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.valueSlider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: var(--thumb-size);
|
||||
height: var(--thumb-size);
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--color-marker-outline);
|
||||
box-shadow: 0 0 0 2px var(--color-marker-shadow);
|
||||
background: var(--thumb-color);
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
/* Left column: mode switch, hex + alpha, gradient endpoints. Right: H S L and R G B columns. */
|
||||
.editor-grid {
|
||||
--editor-grid-gap: 5px;
|
||||
display: flex;
|
||||
gap: var(--editor-grid-gap);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.editor-column:first-of-type {flex:1}
|
||||
.editor-column {
|
||||
display: grid;
|
||||
grid-template-rows: repeat(3, var(--field-height));
|
||||
gap: var(--editor-grid-gap);
|
||||
}
|
||||
|
||||
.hex-row {
|
||||
display: flex;
|
||||
gap: var(--editor-grid-gap);
|
||||
}
|
||||
|
||||
.switch-check {
|
||||
display: none !important;
|
||||
}
|
||||
.switch-label {
|
||||
font-size: 13px;
|
||||
padding: 2px 10px;
|
||||
text-align: center;
|
||||
line-height: 18px;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.switch-slider {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
border-radius: 20px;
|
||||
background: var(--bg-color-alt);
|
||||
border: 2px solid var(--bg-color-alt);
|
||||
height:26px;
|
||||
}
|
||||
.switch-slider:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
left: 0%;
|
||||
border-radius:20px;
|
||||
background: var(--main-color);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.switch-check:checked + .switch-slider:before {
|
||||
left: 50%;
|
||||
}
|
||||
.switch-check:checked + .switch-slider div:first-child,
|
||||
.switch-check + .switch-slider div:last-child{
|
||||
color: var(--fg-color-label);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.switch-check:checked + .switch-slider div:last-child,
|
||||
.switch-check + .switch-slider div:first-child{
|
||||
color: var(--button-fg-light);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
/* H S L / R G B / Hex / Alpha input boxes */
|
||||
.inline-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 5px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-color);
|
||||
}
|
||||
.inline-field > label {
|
||||
width: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--fg-color-text);
|
||||
opacity: 0.6;
|
||||
}
|
||||
.inline-field > input {
|
||||
flex: 1;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0 2px 0 5px;
|
||||
color: var(--fg-color-text);
|
||||
font-size: 15px;
|
||||
line-height: var(--field-height);
|
||||
appearance: textfield; /* No spinner buttons; Up/Down keys and the wheel still step by 1. */
|
||||
}
|
||||
.inline-field:focus-within {
|
||||
border-color: var(--main-color);
|
||||
background: var(--focus-bg-box);
|
||||
}
|
||||
.inline-field:has(input:invalid) {
|
||||
border-color: var(--control-invalid);
|
||||
}
|
||||
.inline-field > input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.inline-field > input[type="number"]::-webkit-inner-spin-button,
|
||||
.inline-field > input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none }
|
||||
|
||||
.value-field { width: var(--number-width)}
|
||||
.field-hex { flex: 1 }
|
||||
#alphaControls {
|
||||
width: calc(var(--number-width) + 2px)
|
||||
}
|
||||
#alphaControls > input {
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
/* Third row holds the gradient bar and the opacity slider in the same cell. The slider only
|
||||
shows while the opacity field or the slider itself is in use, and hides the gradient bar. */
|
||||
.third-row { position: relative }
|
||||
.third-row > * {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.third-row.opacity-open > #opacitySliderBar { display: flex }
|
||||
.third-row.opacity-open > #gradientCtrlBar { display: none }
|
||||
|
||||
#opacitySlider { width: 100%; }
|
||||
#opacitySlider::-webkit-slider-runnable-track {
|
||||
background: var(--track-gradient), var(--checkerboard); /* Transparent end shows the checkerboard through the color. */
|
||||
background-size: auto, 8px 8px;
|
||||
}
|
||||
|
||||
/* Gradient colors: thumbs are evenly spaced and fixed; a click anywhere selects the nearest. */
|
||||
#gradientCtrlBar {
|
||||
display: flex;
|
||||
}
|
||||
.gradient-stops {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
/* Kept 10px away from the gradient bar so a click near its right end can't hit − or +. */
|
||||
.gradient-step-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-left: 10px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.gradient-step-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--thumb-size);
|
||||
height: var(--thumb-size);
|
||||
border-radius: 4px;
|
||||
background: var(--button-bg-normal);
|
||||
color: var(--icon-color);
|
||||
}
|
||||
.gradient-step-button svg { display: block; flex: none }
|
||||
.gradient-step-button:hover:not([aria-disabled="true"]) { background: var(--button-bg-hover) }
|
||||
.gradient-step-button[aria-disabled="true"] { opacity: 0.4; cursor: default }
|
||||
.gradient-track {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
top: 50%;
|
||||
height: var(--thumb-size);
|
||||
transform: translateY(-50%);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
background-image: var(--swatch-color, linear-gradient(transparent, transparent)), var(--checkerboard);
|
||||
background-size: auto, 8px 8px;
|
||||
}
|
||||
.gradient-thumb {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 1px;
|
||||
border: 1px solid var(--color-marker-outline);
|
||||
box-shadow: 0 0 0 1px var(--color-marker-shadow);
|
||||
background: var(--thumb-color);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
/* Thumb i of n sits at 10px + (100% - 20px) * i / (n - 1), set inline as --stop. */
|
||||
.gradient-thumb { left: calc(10px + (100% - 20px) * var(--stop, 0)) }
|
||||
.gradient-thumb[aria-pressed="true"] {
|
||||
width: var(--thumb-size);
|
||||
height: var(--thumb-size);
|
||||
border: 2px solid var(--color-marker-outline);
|
||||
box-shadow: 0 0 0 2px var(--color-marker-shadow);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.gradient-thumb:hover {
|
||||
box-shadow: 0 0 0 3px var(--color-marker-shadow);
|
||||
}
|
||||
|
||||
/* ---------- Original / current / favorites ---------- */
|
||||
.bottom-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
height: var(--preview-size);
|
||||
}
|
||||
|
||||
.color-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--preview-size);
|
||||
height: var(--preview-size);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.color-preview svg {
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.color-preview:hover svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.saved-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, minmax(0, 24px));
|
||||
grid-template-rows: repeat(2, minmax(0, 24px));
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.saved-swatch {
|
||||
position: relative;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.saved-swatch.empty {
|
||||
background: var(--bg-color);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Each layer is separate so transparent RGBA shows the checkerboard beneath it. */
|
||||
.color-swatch, .saved-swatch, .color-preview {
|
||||
background-image: var(--swatch-color, linear-gradient(transparent, transparent)), var(--checkerboard);
|
||||
background-size: auto, 12px 12px;
|
||||
}
|
||||
.color-item { width: 100%; background: transparent; text-align: left; font-family: inherit; }
|
||||
|
||||
/* ---------- Actions ---------- */
|
||||
|
||||
.dialog-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-top: 12px;
|
||||
}
|
||||
.dialog-actions button { margin: 0 }
|
||||
#confirmBtn[disabled] { opacity: 0.5 }
|
||||
.stretch-spacer { flex: 1 }
|
||||
|
||||
.icon-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
background: var(--button-bg-normal);
|
||||
color: var(--fg-color-text);
|
||||
}
|
||||
.icon-button:hover {
|
||||
background: var(--button-bg-hover);
|
||||
}
|
||||
.icon-button[aria-expanded="true"] {
|
||||
background: var(--main-color);
|
||||
color: var(--button-fg-light);
|
||||
}
|
||||
.icon-button[aria-expanded="true"]:hover {
|
||||
background: var(--main-color-hover);
|
||||
}
|
||||
|
||||
.hidden { display: none !important }
|
||||
.invisible { visibility: hidden }
|
||||
|
||||
.color-item:focus-visible, .saved-swatch:focus-visible,
|
||||
select:focus-visible, button:focus-visible { outline: 2px solid var(--main-color); outline-offset: -1px; }
|
||||
@@ -55,13 +55,6 @@
|
||||
/* Speed dial icon tile treatment */
|
||||
--speed-tile-bg: #efefef;
|
||||
--speed-tile-border: #d8d8d8;
|
||||
|
||||
/* Transparency and control markers are UI surfaces, never color data. */
|
||||
--checker-light: #eeeeee;
|
||||
--checker-dark: #aaaaaa;
|
||||
--color-marker-outline: #ffffff;
|
||||
--color-marker-shadow: rgba(0, 0, 0, 0.7);
|
||||
--control-invalid: var(--plugin-status-danger);
|
||||
}
|
||||
|
||||
:root[data-orca-theme="dark"] {
|
||||
@@ -100,8 +93,6 @@
|
||||
/* Speed dial icon tile treatment */
|
||||
--speed-tile-bg: #34343b;
|
||||
--speed-tile-border: #50505a;
|
||||
--checker-light: #68686f;
|
||||
--checker-dark: #414148;
|
||||
}
|
||||
|
||||
/* Re-theme the shared common.css chrome through variables (replaces dark.css's
|
||||
|
||||
@@ -858,10 +858,11 @@ void PrintObject::contour_z()
|
||||
BOOST_LOG_TRIVIAL(debug) << "Contouring in parallel - start";
|
||||
|
||||
TriangleMesh mesh = this->m_model_object->raw_mesh();
|
||||
// A PrintObject groups the instances that share their whole transformation but the XY shift: an
|
||||
// instance rotated, scaled or mirrored differently is a PrintObject of its own. So the first
|
||||
// instance stands for all of them.
|
||||
const ModelInstance *inst = m_instances.front().model_instance;
|
||||
if (m_model_object->instances.size() != 1) {
|
||||
throw RuntimeError("ContourZ: unexpected number of instances");
|
||||
}
|
||||
|
||||
ModelInstance *inst = m_model_object->instances.front();
|
||||
Point center_offset = this->center_offset();
|
||||
Geometry::Transformation trans = inst->get_transformation();
|
||||
|
||||
|
||||
@@ -129,8 +129,6 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Shortcuts.hpp
|
||||
GUI/SpeedDialDialog.cpp
|
||||
GUI/SpeedDialDialog.hpp
|
||||
GUI/ColorPickerDialog.cpp
|
||||
GUI/ColorPickerDialog.hpp
|
||||
GUI/ActionRegistry.cpp
|
||||
GUI/ActionRegistry.hpp
|
||||
GUI/NativeCommands.cpp
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
#include "AMSMaterialsSetting.hpp"
|
||||
#include "ColorPickerDialog.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include <cstdint>
|
||||
#include "ExtrusionCalibration.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
@@ -41,9 +36,11 @@
|
||||
#include <cmath>
|
||||
#include "slic3r/GUI/Widgets/PopupWindow.hpp"
|
||||
#include "slic3r/GUI/Widgets/StaticBox.hpp"
|
||||
#include <wx/colordlg.h>
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/dc.h>
|
||||
#include <wx/colour.h>
|
||||
#include <wx/colourdata.h>
|
||||
#include <wx/dcgraph.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/gdicmn.h>
|
||||
@@ -85,96 +82,6 @@ static std::string float_to_string_with_precision(float value, int precision = 3
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
static std::string colour_to_ams_string(const wxColour& color)
|
||||
{
|
||||
char col_buf[10];
|
||||
sprintf(col_buf, "%02X%02X%02X%02X", (int)color.Red(), (int)color.Green(), (int)color.Blue(), (int)color.Alpha());
|
||||
return col_buf;
|
||||
}
|
||||
|
||||
static wxColour mix_colour(const wxColour& left, const wxColour& right, double ratio)
|
||||
{
|
||||
ratio = std::max(0.0, std::min(1.0, ratio));
|
||||
auto mix_channel = [ratio](unsigned char l, unsigned char r) {
|
||||
return static_cast<unsigned char>(std::round(l + (r - l) * ratio));
|
||||
};
|
||||
return wxColour(mix_channel(left.Red(), right.Red()),
|
||||
mix_channel(left.Green(), right.Green()),
|
||||
mix_channel(left.Blue(), right.Blue()),
|
||||
mix_channel(left.Alpha(), right.Alpha()));
|
||||
}
|
||||
|
||||
static std::vector<ColorPickerPopup::ColorItem> collect_ams_color_items(DevFilaSystem* fila_system)
|
||||
{
|
||||
std::vector<ColorPickerPopup::ColorItem> items;
|
||||
if (!fila_system) return items;
|
||||
|
||||
for (const auto& ams_pair : fila_system->GetAmsList()) {
|
||||
DevAms* ams = ams_pair.second;
|
||||
if (!ams) continue;
|
||||
|
||||
for (const auto& tray_pair : ams->GetTrays()) {
|
||||
DevAmsTray* tray = tray_pair.second;
|
||||
if (!tray || !tray->is_tray_info_ready()) continue;
|
||||
|
||||
ColorPickerPopup::ColorItem item;
|
||||
for (const std::string& col : tray->cols) {
|
||||
item.colors.emplace_back(DevAmsTray::decode_color(col));
|
||||
}
|
||||
if (item.colors.empty() && !tray->color.empty()) {
|
||||
item.colors.emplace_back(DevAmsTray::decode_color(tray->color));
|
||||
}
|
||||
if (item.colors.empty()) continue;
|
||||
|
||||
item.ctype = item.colors.size() > 1 ? tray->ctype : 2;
|
||||
items.emplace_back(std::move(item));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
static std::vector<ColorPickerPopup::ColorItem> collect_preset_color_items(const std::string& filament_id)
|
||||
{
|
||||
std::vector<ColorPickerPopup::ColorItem> items;
|
||||
if (filament_id.empty()) return items;
|
||||
|
||||
auto* clr_query = GUI::wxGetApp().get_filament_color_code_query();
|
||||
if (!clr_query) return items;
|
||||
|
||||
auto* agent = wxGetApp().getAgent();
|
||||
const std::string catalog_id = agent ? agent->from_orca_filament_id(filament_id) : filament_id;
|
||||
FilamentColorCodes* color_codes = clr_query->GetFilaInfoMap(wxString::FromUTF8(catalog_id));
|
||||
if (!color_codes || !color_codes->GetFilamentColor2CodeMap()) return items;
|
||||
|
||||
std::set<std::string> seen;
|
||||
for (const auto& color_pair : *color_codes->GetFilamentColor2CodeMap()) {
|
||||
const FilamentColor& fila_color = color_pair.first;
|
||||
FilamentColorCode* color_code = color_pair.second;
|
||||
if (!color_code || fila_color.GetColors().empty()) continue;
|
||||
|
||||
ColorPickerPopup::ColorItem item;
|
||||
item.ctype = fila_color.ColorCount() < 2 ? 2 :
|
||||
(fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR ? 0 : 1);
|
||||
item.name = color_code->GetFilaColorName();
|
||||
|
||||
std::string key = std::to_string(item.ctype);
|
||||
for (const wxColour& color : fila_color.GetColors()) {
|
||||
item.colors.emplace_back(color);
|
||||
char rgba[9];
|
||||
std::snprintf(rgba, sizeof(rgba), "%02X%02X%02X%02X",
|
||||
static_cast<unsigned>(color.Red()), static_cast<unsigned>(color.Green()),
|
||||
static_cast<unsigned>(color.Blue()), static_cast<unsigned>(color.Alpha()));
|
||||
key += "|" + std::string(rgba);
|
||||
}
|
||||
|
||||
if (item.colors.empty() || !seen.insert(key).second) continue;
|
||||
items.emplace_back(std::move(item));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
AMSMaterialsSetting::AMSMaterialsSetting(wxWindow *parent, wxWindowID id)
|
||||
: DPIDialog(parent, id, _L("AMS Materials Setting"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
|
||||
, m_color_picker_popup(ColorPickerPopup(this))
|
||||
@@ -830,19 +737,8 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
|
||||
nozzle_temp_min.ToLong(&nozzle_temp_min_int);
|
||||
nozzle_temp_max.ToLong(&nozzle_temp_max_int);
|
||||
wxColour color = m_clr_picker->m_colour;
|
||||
std::string tray_color = colour_to_ams_string(color);
|
||||
std::vector<std::string> tray_colors;
|
||||
int tray_ctype = 2;
|
||||
// Only firmware advertising fun2[23] receives the manual multi-color fields.
|
||||
if (obj->is_support_filament_manual_multi_color) {
|
||||
for (const wxColour& selected_color : m_clr_picker->m_cols) {
|
||||
tray_colors.emplace_back(colour_to_ams_string(selected_color));
|
||||
}
|
||||
if (tray_colors.empty()) {
|
||||
tray_colors.emplace_back(tray_color);
|
||||
}
|
||||
tray_ctype = tray_colors.size() > 1 ? m_clr_picker->ctype : 2;
|
||||
}
|
||||
char col_buf[10];
|
||||
sprintf(col_buf, "%02X%02X%02X%02X", (int)color.Red(), (int)color.Green(), (int)color.Blue(), (int)color.Alpha());
|
||||
|
||||
if (ams_filament_id.empty() || nozzle_temp_min.empty() || nozzle_temp_max.empty() || m_filament_type.empty()) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Invalid Setting id";
|
||||
@@ -861,33 +757,13 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
|
||||
<< ", tray_info_idx (filament_id) = " << ams_filament_id
|
||||
<< ", setting_id = " << ams_setting_id
|
||||
<< ", tray_type = " << m_filament_type
|
||||
<< ", tray_color = " << tray_color
|
||||
<< ", tray_color = " << col_buf
|
||||
<< ", nozzle_temp_min = " << nozzle_temp_min_int
|
||||
<< ", nozzle_temp_max = " << nozzle_temp_max_int;
|
||||
|
||||
// set filament
|
||||
if (m_is_third) {
|
||||
obj->command_ams_filament_settings(ams_id, slot_id, ams_filament_id, ams_setting_id, tray_color, m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int,
|
||||
tray_colors, tray_ctype);
|
||||
|
||||
// Refresh the edited tray immediately while its MQTT acknowledgement is pending.
|
||||
DevAmsTray* tray = nullptr;
|
||||
if (is_virtual_tray()) {
|
||||
for (auto& virtual_tray : obj->vt_slot) {
|
||||
if (virtual_tray.id == std::to_string(ams_id)) {
|
||||
tray = &virtual_tray;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tray = obj->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id));
|
||||
}
|
||||
if (tray) {
|
||||
tray->color = tray_color;
|
||||
tray->cols = tray_colors;
|
||||
tray->ctype = tray_ctype;
|
||||
tray->set_hold_count();
|
||||
}
|
||||
obj->command_ams_filament_settings(ams_id, slot_id, ams_filament_id, ams_setting_id, std::string(col_buf), m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int);
|
||||
}
|
||||
|
||||
//reset param
|
||||
@@ -1044,15 +920,8 @@ void AMSMaterialsSetting::set_ctype(int ctype)
|
||||
|
||||
void AMSMaterialsSetting::on_picker_color(wxCommandEvent& event)
|
||||
{
|
||||
std::vector<wxColour> colors = m_color_picker_popup.get_selected_colours();
|
||||
if (colors.empty()) {
|
||||
unsigned int color_num = event.GetInt();
|
||||
colors.emplace_back(color_num >> 24 & 0xFF, color_num >> 16 & 0xFF, color_num >> 8 & 0xFF, color_num & 0xFF);
|
||||
}
|
||||
|
||||
set_ctype(m_color_picker_popup.get_selected_ctype());
|
||||
set_color(colors.front());
|
||||
set_colors(colors);
|
||||
unsigned int color_num = event.GetInt();
|
||||
set_color(wxColour(color_num>>24&0xFF, color_num>>16&0xFF, color_num>>8&0xFF, color_num&0xFF));
|
||||
}
|
||||
|
||||
void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event)
|
||||
@@ -1067,12 +936,14 @@ void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event)
|
||||
}
|
||||
}
|
||||
|
||||
m_color_picker_popup.set_ams_colours(collect_ams_color_items(obj->GetFilaSystem().get()));
|
||||
m_color_picker_popup.set_preset_colours(collect_preset_color_items(ams_filament_id));
|
||||
m_color_picker_popup.set_def_colour(m_clr_picker->m_colour, m_clr_picker->m_cols, m_clr_picker->ctype);
|
||||
std::vector<wxColour> ams_colors;
|
||||
obj->GetFilaSystem()->CollectAmsColors(ams_colors);
|
||||
|
||||
wxPoint img_pos = m_clr_picker->ClientToScreen(wxPoint(0, 0));
|
||||
wxPoint popup_pos(img_pos.x - m_color_picker_popup.GetSize().x - FromDIP(95), img_pos.y - FromDIP(65));
|
||||
m_color_picker_popup.Position(popup_pos, wxSize(0, 0));
|
||||
m_color_picker_popup.set_ams_colours(ams_colors);
|
||||
m_color_picker_popup.set_def_colour(m_clr_picker->m_colour);
|
||||
m_color_picker_popup.Popup();
|
||||
}
|
||||
|
||||
@@ -1861,46 +1732,7 @@ void ColorPicker::doRender(wxDC& dc)
|
||||
auto radius = m_show_full ? size.x / 2 - FromDIP(1) : size.x / 2;
|
||||
if (m_selected) radius -= FromDIP(1);
|
||||
|
||||
if (m_show_full && m_cols.size() > 1) {
|
||||
if (ctype == 0) {
|
||||
const double center_x = size.x / 2.0;
|
||||
const double center_y = size.y / 2.0;
|
||||
const double draw_radius = radius;
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
for (int x = 0; x < size.x; ++x) {
|
||||
const double dx = x + 0.5 - center_x;
|
||||
if (std::abs(dx) > draw_radius) continue;
|
||||
|
||||
const double half_height = std::sqrt(std::max(0.0, draw_radius * draw_radius - dx * dx));
|
||||
const int top = std::max(0, static_cast<int>(std::ceil(center_y - half_height)));
|
||||
const int bottom = std::min(size.y - 1, static_cast<int>(std::floor(center_y + half_height)));
|
||||
const double pos = size.x > 1 ? static_cast<double>(x) / (size.x - 1) : 0.0;
|
||||
const double scaled = pos * (m_cols.size() - 1);
|
||||
const size_t idx = std::min(static_cast<size_t>(scaled), m_cols.size() - 2);
|
||||
const double ratio = scaled - idx;
|
||||
|
||||
if (top <= bottom) {
|
||||
dc.SetBrush(wxBrush(mix_colour(m_cols[idx], m_cols[idx + 1], ratio)));
|
||||
dc.DrawRectangle(x, top, 1, bottom - top + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
float ev_angle = 360.0 / m_cols.size();
|
||||
const float overlap = 2.0f;
|
||||
wxPoint center(size.x / 2, size.y / 2);
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(wxBrush(m_cols.front()));
|
||||
dc.DrawCircle(center.x, center.y, radius);
|
||||
for (int i = 0; i < m_cols.size(); i++) {
|
||||
dc.SetBrush(m_cols[i]);
|
||||
float startAngle = 270.0f + i * ev_angle;
|
||||
float endAngle = startAngle + ev_angle;
|
||||
dc.DrawEllipticArc(center.x - radius, center.y - radius, 2 * radius, 2 * radius, startAngle - overlap, endAngle + overlap);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (alpha == 0) {
|
||||
if (alpha == 0) {
|
||||
wxSize bmp_size = m_bitmap_transparent_def.GetBmpSize();
|
||||
int center_x = (size.x - bmp_size.x) / 2;
|
||||
int center_y = (size.y - bmp_size.y) / 2;
|
||||
@@ -1942,6 +1774,52 @@ void ColorPicker::doRender(wxDC& dc)
|
||||
dc.SetPen(wxPen(wxColour("#6B6B6B")));
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawCircle(size.x / 2, size.y / 2, radius);
|
||||
|
||||
if (m_cols.size() > 1) {
|
||||
if (ctype == 0) {
|
||||
int left = FromDIP(0);
|
||||
float total_width = size.x;
|
||||
int gwidth = std::round(total_width / (m_cols.size() - 1));
|
||||
|
||||
for (int i = 0; i < m_cols.size() - 1; i++) {
|
||||
|
||||
if ((left + gwidth) > (size.x)) {
|
||||
gwidth = size.x - left;
|
||||
}
|
||||
|
||||
auto rect = wxRect(left, 0, gwidth, size.y);
|
||||
dc.GradientFillLinear(rect, m_cols[i], m_cols[i + 1], wxEAST);
|
||||
left += gwidth;
|
||||
}
|
||||
if (wxGetApp().dark_mode()) {
|
||||
dc.DrawBitmap(m_bitmap_border_dark, wxPoint(0, 0));
|
||||
}
|
||||
else {
|
||||
dc.DrawBitmap(m_bitmap_border, wxPoint(0, 0));
|
||||
}
|
||||
}
|
||||
else {
|
||||
float ev_angle = 360.0 / m_cols.size();
|
||||
float startAngle = 270.0;
|
||||
float endAngle = 270.0;
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
for (int i = 0; i < m_cols.size(); i++) {
|
||||
dc.SetBrush(m_cols[i]);
|
||||
endAngle += ev_angle;
|
||||
endAngle = endAngle > 360.0 ? endAngle - 360.0 : endAngle;
|
||||
wxPoint center(size.x / 2, size.y / 2);
|
||||
dc.DrawEllipticArc(center.x - radius, center.y - radius, 2 * radius, 2 * radius, startAngle, endAngle);
|
||||
startAngle += ev_angle;
|
||||
startAngle = startAngle > 360.0 ? startAngle - 360.0 : startAngle;
|
||||
}
|
||||
if (wxGetApp().dark_mode()) {
|
||||
dc.DrawBitmap(m_bitmap_border_dark, wxPoint(0, 0));
|
||||
}
|
||||
else {
|
||||
dc.DrawBitmap(m_bitmap_border, wxPoint(0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_is_empty) {
|
||||
@@ -2010,21 +1888,19 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
|
||||
m_ams_fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
//other
|
||||
m_other_fg_sizer = new wxFlexGridSizer(0, 8, 0, 0);
|
||||
m_other_fg_sizer->SetFlexibleDirection(wxBOTH);
|
||||
m_other_fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
wxFlexGridSizer* fg_sizer;
|
||||
fg_sizer = new wxFlexGridSizer(0, 8, 0, 0);
|
||||
fg_sizer->SetFlexibleDirection(wxBOTH);
|
||||
fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
|
||||
for (wxColour col : m_def_colors) {
|
||||
auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
cp->set_color(col);
|
||||
cp->set_colors({col});
|
||||
cp->ctype = 2;
|
||||
cp->set_selected(false);
|
||||
cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238)));
|
||||
m_color_pickers.push_back(cp);
|
||||
m_default_color_pickers.push_back(cp);
|
||||
m_other_fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
|
||||
fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
|
||||
cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) {
|
||||
set_def_colour(cp->m_colour);
|
||||
|
||||
@@ -2087,11 +1963,16 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
|
||||
sizer_custom->Add(m_ts_stbitmap_custom, 0, wxEXPAND, 0);
|
||||
m_custom_cp->Layout();
|
||||
|
||||
m_clrData = new wxColourData();
|
||||
m_clrData->SetChooseFull(true);
|
||||
m_clrData->SetChooseAlpha(false);
|
||||
|
||||
|
||||
m_sizer_box->Add(0, 0, 0, wxTOP, FromDIP(10));
|
||||
m_sizer_box->Add(m_sizer_ams, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_ams_fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_sizer_other, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_other_fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_sizer_custom, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_custom_cp, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(16));
|
||||
m_sizer_box->Add(0, 0, 0, wxTOP, FromDIP(10));
|
||||
@@ -2102,7 +1983,7 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
|
||||
m_def_color_box->Fit();
|
||||
|
||||
m_sizer_main->Add(m_def_color_box, 0, wxALL | wxEXPAND, 10);
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
SetSizer(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
@@ -2110,45 +1991,55 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
|
||||
void ColorPickerPopup::on_custom_clr_picker(wxMouseEvent& /*event*/)
|
||||
void ColorPickerPopup::on_custom_clr_picker(wxMouseEvent& event)
|
||||
{
|
||||
const bool was_shown = IsShown();
|
||||
std::optional<wxColour> picker_color;
|
||||
{
|
||||
const ColorRGBA initial(m_def_col.Red(), m_def_col.Green(), m_def_col.Blue(), static_cast<unsigned char>(255));
|
||||
// Snapshot the custom control's anchor before releasing the transient popup grab.
|
||||
ColorPickerDialog dialog(GetParent(), initial, {}, false, m_custom_cp);
|
||||
Dismiss();
|
||||
if (dialog.ShowModal() == wxID_OK && dialog.selection()) {
|
||||
const auto& result = std::get<ColorRGBA>(*dialog.selection());
|
||||
picker_color = wxColour(static_cast<unsigned char>(std::lround(result.r() * 255.f)),
|
||||
static_cast<unsigned char>(std::lround(result.g() * 255.f)),
|
||||
static_cast<unsigned char>(std::lround(result.b() * 255.f)));
|
||||
}
|
||||
} // Destroy the modal before restoring the first-level popup and its focus/grab.
|
||||
if (!picker_color) {
|
||||
if (was_shown) Popup();
|
||||
return;
|
||||
std::vector<std::string> colors = wxGetApp().app_config->get_custom_color_from_config();
|
||||
for (int i = 0; i < colors.size(); i++) {
|
||||
m_clrData->SetCustomColour(i, string_to_wxColor(colors[i]));
|
||||
}
|
||||
auto clr_dialog = new wxColourDialog(nullptr, m_clrData);
|
||||
wxColour picker_color;
|
||||
|
||||
set_def_colour(*picker_color);
|
||||
wxCommandEvent evt(EVT_SELECTED_COLOR);
|
||||
const std::uint32_t packed = (std::uint32_t(picker_color->Red()) << 24) |
|
||||
(std::uint32_t(picker_color->Green()) << 16) |
|
||||
(std::uint32_t(picker_color->Blue()) << 8) | 255u;
|
||||
evt.SetInt(static_cast<int>(packed));
|
||||
wxPostEvent(GetParent(), evt);
|
||||
if (clr_dialog->ShowModal() == wxID_OK) {
|
||||
m_clrData = &(clr_dialog->GetColourData());
|
||||
if (colors.size() != CUSTOM_COLOR_COUNT) {
|
||||
colors.resize(CUSTOM_COLOR_COUNT);
|
||||
}
|
||||
for (int i = 0; i < CUSTOM_COLOR_COUNT; i++) {
|
||||
colors[i] = color_to_string(m_clrData->GetCustomColour(i));
|
||||
}
|
||||
wxGetApp().app_config->save_custom_color_to_config(colors);
|
||||
|
||||
picker_color = wxColour(
|
||||
m_clrData->GetColour().Red(),
|
||||
m_clrData->GetColour().Green(),
|
||||
m_clrData->GetColour().Blue(),
|
||||
255
|
||||
);
|
||||
|
||||
if (picker_color.Alpha() == 0) {
|
||||
m_ts_stbitmap_custom->Show();
|
||||
}
|
||||
else {
|
||||
m_ts_stbitmap_custom->Hide();
|
||||
m_custom_cp->SetBackgroundColor(picker_color);
|
||||
}
|
||||
|
||||
set_def_colour(picker_color);
|
||||
wxCommandEvent evt(EVT_SELECTED_COLOR);
|
||||
unsigned long g_col = ((picker_color.Red() & 0xff) << 24) + ((picker_color.Green() & 0xff) << 16) + ((picker_color.Blue() & 0xff) << 8) + (picker_color.Alpha() & 0xff);
|
||||
evt.SetInt(g_col);
|
||||
wxPostEvent(GetParent(), evt);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorPickerPopup::set_ams_colours(const std::vector<ColorItem>& ams)
|
||||
void ColorPickerPopup::set_ams_colours(std::vector<wxColour> ams)
|
||||
{
|
||||
m_ams_fg_sizer->Clear(false);
|
||||
if (m_ams_color_pickers.size() > 0) {
|
||||
for (ColorPicker* col_pick:m_ams_color_pickers) {
|
||||
|
||||
std::vector<ColorPicker*>::iterator iter = find(m_color_pickers.begin(), m_color_pickers.end(), col_pick);
|
||||
if (iter != m_color_pickers.end()) {
|
||||
col_pick->Hide();
|
||||
col_pick->Destroy();
|
||||
m_color_pickers.erase(iter);
|
||||
}
|
||||
@@ -2158,22 +2049,17 @@ void ColorPickerPopup::set_ams_colours(const std::vector<ColorItem>& ams)
|
||||
}
|
||||
|
||||
|
||||
m_ams_color_items = ams;
|
||||
for (const ColorItem& item : m_ams_color_items) {
|
||||
if (item.colors.empty()) continue;
|
||||
|
||||
m_ams_colors = ams;
|
||||
for (wxColour col : m_ams_colors) {
|
||||
auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
cp->set_color(item.colors.front());
|
||||
cp->set_colors(item.colors);
|
||||
cp->ctype = item.colors.size() > 1 ? item.ctype : 2;
|
||||
cp->set_show_full(true);
|
||||
cp->set_color(col);
|
||||
cp->set_selected(false);
|
||||
cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238)));
|
||||
m_color_pickers.push_back(cp);
|
||||
m_ams_color_pickers.push_back(cp);
|
||||
m_ams_fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
|
||||
cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) {
|
||||
set_def_colour(cp->m_colour, cp->m_cols, cp->ctype);
|
||||
set_def_colour(cp->m_colour);
|
||||
|
||||
wxCommandEvent evt(EVT_SELECTED_COLOR);
|
||||
unsigned long g_col = ((cp->m_colour.Red() & 0xff) << 24) + ((cp->m_colour.Green() & 0xff) << 16) + ((cp->m_colour.Blue() & 0xff) << 8) + (cp->m_colour.Alpha() & 0xff);
|
||||
@@ -2186,68 +2072,9 @@ void ColorPickerPopup::set_ams_colours(const std::vector<ColorItem>& ams)
|
||||
Fit();
|
||||
}
|
||||
|
||||
void ColorPickerPopup::set_preset_colours(const std::vector<ColorItem>& preset_colors)
|
||||
{
|
||||
m_other_fg_sizer->Clear(false);
|
||||
if (!m_preset_color_pickers.empty()) {
|
||||
for (ColorPicker* col_pick : m_preset_color_pickers) {
|
||||
auto iter = std::find(m_color_pickers.begin(), m_color_pickers.end(), col_pick);
|
||||
if (iter != m_color_pickers.end()) {
|
||||
col_pick->Hide();
|
||||
col_pick->Destroy();
|
||||
m_color_pickers.erase(iter);
|
||||
}
|
||||
}
|
||||
m_preset_color_pickers.clear();
|
||||
}
|
||||
|
||||
for (const ColorItem& item : preset_colors) {
|
||||
if (item.colors.empty()) continue;
|
||||
|
||||
auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
cp->set_color(item.colors.front());
|
||||
cp->set_colors(item.colors);
|
||||
cp->ctype = item.colors.size() > 1 ? item.ctype : 2;
|
||||
cp->set_show_full(true);
|
||||
cp->set_selected(false);
|
||||
cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238)));
|
||||
if (!item.name.empty()) {
|
||||
cp->SetToolTip(item.name);
|
||||
}
|
||||
|
||||
m_color_pickers.push_back(cp);
|
||||
m_preset_color_pickers.push_back(cp);
|
||||
cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) {
|
||||
set_def_colour(cp->m_colour, cp->m_cols, cp->ctype);
|
||||
|
||||
wxCommandEvent evt(EVT_SELECTED_COLOR);
|
||||
unsigned long g_col = ((cp->m_colour.Red() & 0xff) << 24) + ((cp->m_colour.Green() & 0xff) << 16) + ((cp->m_colour.Blue() & 0xff) << 8) + (cp->m_colour.Alpha() & 0xff);
|
||||
evt.SetInt(g_col);
|
||||
wxPostEvent(GetParent(), evt);
|
||||
});
|
||||
}
|
||||
|
||||
const bool show_default_colours = m_preset_color_pickers.empty();
|
||||
for (ColorPicker* cp : m_default_color_pickers) {
|
||||
cp->Show(show_default_colours);
|
||||
}
|
||||
for (ColorPicker* cp : (show_default_colours ? m_default_color_pickers : m_preset_color_pickers)) {
|
||||
m_other_fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
|
||||
}
|
||||
|
||||
m_other_fg_sizer->Layout();
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
void ColorPickerPopup::set_def_colour(wxColour col, std::vector<wxColour> cols, int ctype)
|
||||
void ColorPickerPopup::set_def_colour(wxColour col)
|
||||
{
|
||||
m_def_col = col;
|
||||
if (cols.empty()) {
|
||||
cols.push_back(col);
|
||||
}
|
||||
m_def_cols = cols;
|
||||
m_def_ctype = m_def_cols.size() > 1 ? ctype : 2;
|
||||
|
||||
for (ColorPicker* cp : m_color_pickers) {
|
||||
if (cp->m_selected) {
|
||||
@@ -2256,8 +2083,7 @@ void ColorPickerPopup::set_def_colour(wxColour col, std::vector<wxColour> cols,
|
||||
}
|
||||
|
||||
for (ColorPicker* cp : m_color_pickers) {
|
||||
if (!cp->IsShown()) continue;
|
||||
if (cp->ctype == m_def_ctype && cp->m_cols == m_def_cols) {
|
||||
if (cp->m_colour == m_def_col) {
|
||||
cp->set_selected(true);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -82,22 +82,15 @@ public:
|
||||
class ColorPickerPopup : public PopupWindow
|
||||
{
|
||||
public:
|
||||
struct ColorItem
|
||||
{
|
||||
std::vector<wxColour> colors;
|
||||
int ctype = 2;
|
||||
wxString name;
|
||||
};
|
||||
|
||||
ScalableBitmap m_ts_bitmap_custom;
|
||||
wxStaticBitmap* m_ts_stbitmap_custom;
|
||||
StaticBox* m_custom_cp;
|
||||
wxColourData* m_clrData;
|
||||
StaticBox* m_def_color_box;
|
||||
wxFlexGridSizer* m_ams_fg_sizer;
|
||||
wxFlexGridSizer* m_other_fg_sizer;
|
||||
wxColour m_def_col;
|
||||
std::vector<wxColour> m_def_colors;
|
||||
std::vector<ColorItem> m_ams_color_items;
|
||||
std::vector<wxColour> m_ams_colors;
|
||||
std::vector<ColorPicker*> m_color_pickers;
|
||||
std::vector<ColorPicker*> m_ams_color_pickers;
|
||||
|
||||
@@ -105,20 +98,13 @@ public:
|
||||
ColorPickerPopup(wxWindow* parent);
|
||||
~ColorPickerPopup() {};
|
||||
void on_custom_clr_picker(wxMouseEvent& event);
|
||||
void set_ams_colours(const std::vector<ColorItem>& ams);
|
||||
void set_preset_colours(const std::vector<ColorItem>& preset_colors);
|
||||
void set_def_colour(wxColour col, std::vector<wxColour> cols = {}, int ctype = 2);
|
||||
const std::vector<wxColour>& get_selected_colours() const { return m_def_cols; }
|
||||
int get_selected_ctype() const { return m_def_ctype; }
|
||||
void set_ams_colours(std::vector<wxColour> ams);
|
||||
void set_def_colour(wxColour col);
|
||||
void paintEvent(wxPaintEvent& evt);
|
||||
virtual void OnDismiss() wxOVERRIDE;
|
||||
virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE;
|
||||
|
||||
private:
|
||||
std::vector<ColorPicker*> m_default_color_pickers;
|
||||
std::vector<ColorPicker*> m_preset_color_pickers;
|
||||
std::vector<wxColour> m_def_cols;
|
||||
int m_def_ctype = 2;
|
||||
public:
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -747,9 +747,7 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model& model, const Dyn
|
||||
|
||||
// Orca: prevent resetting under gcode viewer mode
|
||||
if (invalidated != PrintBase::APPLY_STATUS_UNCHANGED) {
|
||||
// Use the app’s plater accessor instead of going through mainframe, otherwise anything during startup that reaches apply()
|
||||
// (loading presets, config or input files, e.g. from the CLI) therefore dereferences a null `mainframe` and crashes.
|
||||
const auto plater = GUI::wxGetApp().plater();
|
||||
const auto plater = GUI::wxGetApp().mainframe->m_plater;
|
||||
if (plater && plater->only_gcode_mode()) {
|
||||
invalidated = PrintBase::APPLY_STATUS_UNCHANGED;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,92 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include "Widgets/WebViewHostDialog.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <variant>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <wx/gdicmn.h>
|
||||
|
||||
class wxWindow;
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
// Most colors an ordered gradient may hold. This is the native switch for multi-color gradients:
|
||||
// 2 keeps the classic two-color gradients. The page receives it as options.max_gradient_colors
|
||||
// and never offers more, so the picker, favorites and project colors all share one limit.
|
||||
inline constexpr std::size_t COLOR_PICKER_MAX_GRADIENT_COLORS = 7;
|
||||
|
||||
// Gradient colors retain their order (2..COLOR_PICKER_MAX_GRADIENT_COLORS); a solid has exactly one color.
|
||||
using ColorGradient = std::vector<ColorRGBA>;
|
||||
using ColorSelection = std::variant<ColorRGBA, ColorGradient>;
|
||||
|
||||
struct ColorPickerOptions {
|
||||
bool allow_gradient = false;
|
||||
bool allow_alpha = false;
|
||||
};
|
||||
|
||||
// Project storage preserves direction; FilamentColor's set is only for the
|
||||
// official palette and cannot represent ordered custom gradients.
|
||||
struct FilamentColorPickerValue {
|
||||
std::vector<std::string> colors;
|
||||
bool gradient = false;
|
||||
std::string primary;
|
||||
};
|
||||
|
||||
std::optional<ColorSelection> filament_color_picker_selection(const FilamentColorPickerValue& value);
|
||||
ColorSelection filament_color_picker_initial(const FilamentColorPickerValue& value);
|
||||
FilamentColorPickerValue filament_color_picker_result(const ColorSelection& selection);
|
||||
bool filament_color_picker_changed(const ColorSelection& initial, const std::optional<ColorSelection>& result);
|
||||
|
||||
class ColorPickerDialog : public WebViewHostDialog
|
||||
{
|
||||
public:
|
||||
ColorPickerDialog(wxWindow* parent, const ColorSelection& initial, ColorPickerOptions options = {},
|
||||
bool preserve_multi_color = false, wxWindow* anchor = nullptr);
|
||||
~ColorPickerDialog() override;
|
||||
|
||||
const std::optional<ColorSelection>& selection() const { return m_selection; }
|
||||
|
||||
private:
|
||||
void add_user_scripts() override;
|
||||
void on_script_message(const nlohmann::json& payload) override;
|
||||
void handle_web_command(const nlohmann::json& payload);
|
||||
void send_initial_state();
|
||||
void finish(int return_code);
|
||||
void position_panel();
|
||||
void move_to_anchor();
|
||||
void resize_to_content(int height);
|
||||
void focus_webview();
|
||||
void repaint_webview();
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
void on_sys_color_changed() override;
|
||||
|
||||
ColorSelection m_initial;
|
||||
ColorPickerOptions m_options;
|
||||
std::optional<ColorSelection> m_selection;
|
||||
std::vector<ColorSelection> m_favorites;
|
||||
std::string m_page_id;
|
||||
bool m_preserve_multi_color = false;
|
||||
bool m_init_sent = false;
|
||||
bool m_page_ready = false;
|
||||
bool m_closing = false;
|
||||
bool m_favorites_writable = false;
|
||||
bool m_sidebar_open = false;
|
||||
std::string m_palette;
|
||||
bool m_initial_positioned = false;
|
||||
wxRect m_anchor_rect;
|
||||
wxRect m_work_area;
|
||||
int m_content_height = 360;
|
||||
int m_content_width = 330;
|
||||
bool m_positioning = false;
|
||||
std::shared_ptr<std::atomic<bool>> m_alive = std::make_shared<std::atomic<bool>>(true);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
@@ -1798,8 +1798,7 @@ int MachineObject::command_ams_calibrate(int ams_id)
|
||||
return command_with_dialog(m_agent->command_ams_calibrate(get_dev_id(), ams_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type,
|
||||
int nozzle_temp_min, int nozzle_temp_max, const std::vector<std::string>& tray_colors, int tray_ctype)
|
||||
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max)
|
||||
{
|
||||
int tag_tray_id = 0;
|
||||
int tag_ams_id = ams_id;
|
||||
@@ -1828,10 +1827,6 @@ int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::s
|
||||
j["print"]["nozzle_temp_min"] = nozzle_temp_min;
|
||||
j["print"]["nozzle_temp_max"] = nozzle_temp_max;
|
||||
j["print"]["tray_type"] = tray_type;
|
||||
if (!tray_colors.empty()) {
|
||||
j["print"]["cols"] = tray_colors;
|
||||
j["print"]["ctype"] = tray_ctype;
|
||||
}
|
||||
|
||||
return this->publish_json(j);
|
||||
}
|
||||
@@ -5483,7 +5478,6 @@ void MachineObject::parse_new_info(json print)
|
||||
is_support_pa_mode = (get_flag_bits_no_border(fun2, 3) == 1);
|
||||
is_support_remote_dry = (get_flag_bits_no_border(fun2, 5) == 1);
|
||||
is_support_check_track_switch_match_slice_printer = get_flag_bits_no_border(fun2, 19) == 1;
|
||||
is_support_filament_manual_multi_color = (get_flag_bits_no_border(fun2, 23) == 1);
|
||||
|
||||
if (DevPrinterConfigUtil::support_print_check_firmware_for_tpu_left(printer_type)) {
|
||||
m_firmware_support_print_tpu_left = get_flag_bits_no_border(fun2, 7) == 1;
|
||||
|
||||
@@ -674,7 +674,6 @@ public:
|
||||
bool is_support_remote_dry = false;
|
||||
bool is_support_check_track_switch_match_slice_printer{false};
|
||||
bool is_support_pa_mode{false};
|
||||
bool is_support_filament_manual_multi_color{false};
|
||||
std::optional<bool> m_firmware_support_print_tpu_left;
|
||||
|
||||
bool installed_upgrade_kit{false};
|
||||
@@ -805,7 +804,7 @@ public:
|
||||
int command_ams_switch_filament(bool switch_filament);
|
||||
int command_ams_air_print_detect(bool air_print_detect);
|
||||
int command_ams_calibrate(int ams_id);
|
||||
int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max, const std::vector<std::string>& tray_colors = {}, int tray_ctype = 2);
|
||||
int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max);
|
||||
int command_ams_select_tray(std::string tray_id);
|
||||
int command_ams_refresh_rfid(int ams_id, int slot_id);
|
||||
int command_ams_control(std::string action);
|
||||
|
||||
@@ -96,7 +96,7 @@ void FilamentColorCodeQuery::LoadFromLocal()
|
||||
const auto& fila_color_strs = json_data_item["fila_color"].get<std::vector<wxString>>();
|
||||
for (const auto& color_str : fila_color_strs) {
|
||||
if (color_str.size() > 3) /* Skip the value like "#0"*/{
|
||||
fila_color.AddColor(wxColour(color_str));
|
||||
fila_color.m_colors.emplace(wxColour(color_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,17 +87,6 @@ struct FilamentColor
|
||||
|
||||
public:
|
||||
size_t ColorCount() const noexcept { return m_colors.size(); }
|
||||
// Preserve catalog endpoint order for display while retaining set-based lookup identity.
|
||||
const std::vector<wxColour>& GetColors() const noexcept { return m_color_list; }
|
||||
void AddColor(const wxColour& color)
|
||||
{
|
||||
if (m_colors.insert(color).second) m_color_list.push_back(color);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<wxColour> m_color_list;
|
||||
|
||||
public:
|
||||
|
||||
void EndSet(int ctype)
|
||||
{
|
||||
|
||||
+166
-75
@@ -2,7 +2,6 @@
|
||||
#include "GUI_App.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Field.hpp"
|
||||
#include "ColorPickerDialog.hpp"
|
||||
#include "libslic3r/GCode/Thumbnails.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "Plater.hpp"
|
||||
@@ -46,11 +45,7 @@
|
||||
#include <wx/arrstr.h>
|
||||
#include <wx/anybutton.h>
|
||||
#include <wx/colour.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/brush.h>
|
||||
#include <wx/pen.h>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include <wx/clrpicker.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/dcmemory.h>
|
||||
#include <wx/dc.h>
|
||||
@@ -2623,41 +2618,92 @@ void PluginConfigField::msw_rescale()
|
||||
|
||||
void ColourPicker::BUILD()
|
||||
{
|
||||
// An empty borderless wxButton is created with zero height on macOS. Give the bitmap
|
||||
// field the same height as other parameter buttons before drawing its swatch.
|
||||
wxSize size(def_width_wider() * m_em_unit, m_parent->FromDIP(26));
|
||||
auto size = wxSize(def_width_wider() * m_em_unit, -1); // ORCA match color picker width
|
||||
if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit);
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
|
||||
|
||||
const wxString clr_str(m_opt.type == coString ? m_opt.get_default_value<ConfigOptionString>()->value :
|
||||
m_opt.get_default_value<ConfigOptionStrings>()->get_at(m_opt_idx));
|
||||
auto* button = new wxButton(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size, wxBORDER_NONE);
|
||||
window = button;
|
||||
button->SetBitmapMargins(0, 0);
|
||||
// Validate the color
|
||||
wxString clr_str(m_opt.type == coString ? m_opt.get_default_value<ConfigOptionString>()->value : m_opt.get_default_value<ConfigOptionStrings>()->get_at(m_opt_idx));
|
||||
wxColour clr(clr_str);
|
||||
if (clr_str.IsEmpty() || !clr.IsOk()) {
|
||||
clr = wxTransparentColour;
|
||||
}
|
||||
|
||||
auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size);
|
||||
if (parent_is_custom_ctrl && m_opt.height < 0)
|
||||
opt_height = double(button->GetSize().GetHeight()) / m_em_unit;
|
||||
button->SetFont(wxGetApp().normal_font());
|
||||
if (!wxOSX) button->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
set_value(boost::any(clr_str));
|
||||
button->Bind(wxEVT_BUTTON, &ColourPicker::on_button_click, this);
|
||||
button->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) {
|
||||
apply_user_color(wxTransparentColour);
|
||||
event.Skip();
|
||||
opt_height = (double)temp->GetSize().GetHeight() / m_em_unit;
|
||||
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
convert_to_picker_widget(temp);
|
||||
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
wxGetApp().UpdateDarkUI(temp->GetPickerCtrl());
|
||||
|
||||
// // recast as a wxWindow to fit the calling convention
|
||||
window = dynamic_cast<wxWindow*>(temp);
|
||||
|
||||
temp->Bind(wxEVT_COLOURPICKER_CHANGED, ([
|
||||
#ifdef __WXMSW__
|
||||
temp,
|
||||
#endif
|
||||
this](wxCommandEvent e) {
|
||||
#ifdef __WXMSW__
|
||||
draw_bmp_btn(temp, temp->GetColour());
|
||||
#endif
|
||||
on_change_field();
|
||||
}), temp->GetId());
|
||||
|
||||
// ORCA reset value to default on right click. previously no way to switch back on windows
|
||||
temp->GetPickerCtrl()->Bind(wxEVT_RIGHT_DOWN, [this, temp](wxMouseEvent e){
|
||||
#ifdef __WXMSW__
|
||||
temp->SetColour(wxTransparentColour);
|
||||
draw_bmp_btn(temp, wxTransparentColour);
|
||||
#else
|
||||
set_undef_value(temp);
|
||||
#endif
|
||||
on_change_field();
|
||||
e.Skip();
|
||||
});
|
||||
// Native layouts may assign the final field size after construction.
|
||||
button->Bind(wxEVT_SIZE, [this](wxSizeEvent& event) {
|
||||
draw_bmp_btn();
|
||||
event.Skip();
|
||||
});
|
||||
button->SetToolTip(get_tooltip_text(clr_str));
|
||||
|
||||
temp->SetToolTip(get_tooltip_text(clr_str));
|
||||
}
|
||||
|
||||
void ColourPicker::draw_bmp_btn()
|
||||
void ColourPicker::set_undef_value(wxColourPickerCtrl* field)
|
||||
{
|
||||
if (!window) return;
|
||||
auto* btn = static_cast<wxButton*>(window);
|
||||
if (btn->GetSize().x <= 0 || btn->GetSize().y <= 0) return;
|
||||
field->SetColour(wxTransparentColour);
|
||||
|
||||
wxButton* btn = dynamic_cast<wxButton*>(field->GetPickerCtrl());
|
||||
if (!btn->GetBitmap().IsOk()) return;
|
||||
|
||||
wxImage image(btn->GetBitmap().GetSize());
|
||||
image.InitAlpha();
|
||||
memset(image.GetAlpha(), 0, image.GetWidth() * image.GetHeight());
|
||||
wxBitmap bmp(std::move(image));
|
||||
wxMemoryDC dc(bmp);
|
||||
if (!dc.IsOk()) return;
|
||||
#ifdef __WXMSW__
|
||||
wxGCDC dc2(dc);
|
||||
#else
|
||||
wxDC &dc2(dc);
|
||||
#endif
|
||||
dc2.SetPen(wxPen("#F1754E", 1));
|
||||
|
||||
const wxRect rect = wxRect(0, 0, bmp.GetWidth(), bmp.GetHeight());
|
||||
dc2.DrawLine(rect.GetLeftBottom(), rect.GetTopRight());
|
||||
|
||||
dc.SelectObject(wxNullBitmap);
|
||||
btn->SetBitmapLabel(bmp);
|
||||
}
|
||||
|
||||
// ORCA match style with button on windows
|
||||
void ColourPicker::draw_bmp_btn(wxColourPickerCtrl* field, wxColour color)
|
||||
{
|
||||
wxButton* btn = dynamic_cast<wxButton*>(field->GetPickerCtrl());
|
||||
|
||||
if (!btn->GetBitmap().IsOk()) return;
|
||||
btn->SetWindowStyle(wxBORDER_NONE); // ORCA just in case to prevent any overflow
|
||||
btn->SetBackgroundColour(*wxWHITE);
|
||||
wxGetApp().UpdateDarkUI(btn);
|
||||
|
||||
auto create_bitmap = [btn](const wxColour& picker_color,const wxColour& bg_color, bool focus) -> wxBitmap {
|
||||
wxSize btn_sz = btn->GetSize();
|
||||
wxImage image(btn_sz);
|
||||
@@ -2666,11 +2712,11 @@ void ColourPicker::draw_bmp_btn()
|
||||
wxBitmap bmp(std::move(image));
|
||||
wxMemoryDC dc(bmp);
|
||||
if (!dc.IsOk()) return bmp;
|
||||
wxGCDC dc2(dc);
|
||||
wxGCDC dc2(dc); // just use wxGCDC since bitmap button only used for windows
|
||||
|
||||
dc2.SetPen(focus ? wxPen(wxColour(StateColor::darkModeColorFor(wxColour("#009688"))), 1) : *wxTRANSPARENT_PEN);
|
||||
dc2.SetBrush(wxBrush(StateColor::darkModeColorFor(bg_color)));
|
||||
dc2.DrawRoundedRectangle(wxRect(wxPoint(0, 0), btn_sz), btn->FromDIP(4));
|
||||
dc2.DrawRoundedRectangle(btn->GetRect(), btn->FromDIP(4));
|
||||
|
||||
int padding = btn->FromDIP(5);
|
||||
dc2.SetPen(*wxTRANSPARENT_PEN);
|
||||
@@ -2689,72 +2735,117 @@ void ColourPicker::draw_bmp_btn()
|
||||
return bmp;
|
||||
};
|
||||
|
||||
btn->SetBitmap( create_bitmap(m_colour, wxColour("#DFDFDF"), false)); // Normal
|
||||
btn->SetBitmapFocus( create_bitmap(m_colour, wxColour("#DFDFDF"), true )); // Focus
|
||||
btn->SetBitmapCurrent( create_bitmap(m_colour, wxColour("#D4D4D4"), false)); // Hover
|
||||
btn->SetBitmap( create_bitmap(color, wxColour("#DFDFDF"), false)); // Normal
|
||||
btn->SetBitmapFocus( create_bitmap(color, wxColour("#DFDFDF"), true )); // Focus
|
||||
btn->SetBitmapCurrent( create_bitmap(color, wxColour("#D4D4D4"), false)); // Hover
|
||||
}
|
||||
|
||||
void ColourPicker::set_value(const std::string& value, bool change_event)
|
||||
void ColourPicker::set_value(const boost::any& value, bool change_event)
|
||||
{
|
||||
set_value(boost::any(wxString::FromUTF8(value)), change_event);
|
||||
}
|
||||
m_disable_change_event = !change_event;
|
||||
const wxString clr_str(boost::any_cast<wxString>(value));
|
||||
auto field = dynamic_cast<wxColourPickerCtrl*>(window);
|
||||
|
||||
void ColourPicker::set_value(const boost::any& value, bool /*change_event*/)
|
||||
{
|
||||
// Like wxColourPickerCtrl::SetColour, programmatic assignment is silent.
|
||||
const wxString clr_str = boost::any_cast<wxString>(value);
|
||||
const wxColour parsed(clr_str);
|
||||
m_colour = clr_str.IsEmpty() || !parsed.IsOk() ? wxTransparentColour : parsed;
|
||||
draw_bmp_btn();
|
||||
#ifdef __WXMSW__
|
||||
const wxColour parsed_clr(clr_str);
|
||||
wxColour clr = (clr_str.IsEmpty() || !parsed_clr.IsOk()) ? wxTransparentColour : parsed_clr;
|
||||
field->SetColour(clr);
|
||||
draw_bmp_btn(field, clr);
|
||||
#else
|
||||
wxColour clr(clr_str);
|
||||
if (clr_str.IsEmpty() || !clr.IsOk())
|
||||
set_undef_value(field);
|
||||
else
|
||||
field->SetColour(clr);
|
||||
#endif
|
||||
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
boost::any& ColourPicker::get_value()
|
||||
{
|
||||
m_value = m_colour == wxTransparentColour ? std::string() :
|
||||
encode_color(ColorRGB(m_colour.Red(), m_colour.Green(), m_colour.Blue()));
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void ColourPicker::apply_user_color(const std::optional<wxColour>& color)
|
||||
{
|
||||
if (!color || !color->IsOk() || *color == m_colour) return;
|
||||
const std::string previous = boost::any_cast<std::string>(get_value());
|
||||
m_colour = *color;
|
||||
draw_bmp_btn();
|
||||
if (previous != boost::any_cast<std::string>(get_value())) on_change_field();
|
||||
save_colors_to_config();
|
||||
auto colour = static_cast<wxColourPickerCtrl*>(window)->GetColour();
|
||||
if (colour == wxTransparentColour)
|
||||
m_value = std::string("");
|
||||
else {
|
||||
m_value = encode_color(ColorRGB(colour.Red(), colour.Green(), colour.Blue()));
|
||||
}
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void ColourPicker::msw_rescale()
|
||||
{
|
||||
Field::msw_rescale();
|
||||
wxSize size(def_width_wider() * m_em_unit, m_parent->FromDIP(26));
|
||||
|
||||
wxColourPickerCtrl* field = dynamic_cast<wxColourPickerCtrl*>(window);
|
||||
auto size = wxSize(def_width_wider() * m_em_unit, -1); // ORCA match color picker width with parameters
|
||||
if (m_opt.height >= 0)
|
||||
size.SetHeight(m_opt.height * m_em_unit);
|
||||
else if (parent_is_custom_ctrl && opt_height > 0)
|
||||
size.SetHeight(lround(opt_height * m_em_unit));
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width * m_em_unit);
|
||||
if (parent_is_custom_ctrl) window->SetSize(size);
|
||||
else window->SetMinSize(size);
|
||||
draw_bmp_btn();
|
||||
if (parent_is_custom_ctrl)
|
||||
field->SetSize(size);
|
||||
else
|
||||
field->SetMinSize(size);
|
||||
|
||||
#ifdef __WXMSW__
|
||||
draw_bmp_btn(field, field->GetColour());
|
||||
#else
|
||||
if (field->GetColour() == wxTransparentColour)
|
||||
set_undef_value(field);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void ColourPicker::sys_color_changed()
|
||||
{
|
||||
if (window) wxGetApp().UpdateDarkUI(window, true);
|
||||
draw_bmp_btn();
|
||||
#ifdef _WIN32
|
||||
if (wxWindow* win = this->getWindow())
|
||||
if (wxColourPickerCtrl* picker = dynamic_cast<wxColourPickerCtrl*>(win)){
|
||||
wxGetApp().UpdateDarkUI(picker->GetPickerCtrl(), true);
|
||||
draw_bmp_btn(picker, picker->GetColour());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColourPicker::on_button_click(wxCommandEvent& /*event*/)
|
||||
{
|
||||
const wxColour initial = m_colour == wxTransparentColour ? wxColour(0, 0, 0) : m_colour;
|
||||
const ColorRGBA color(initial.Red(), initial.Green(), initial.Blue(), static_cast<unsigned char>(255));
|
||||
ColorPickerDialog dialog(m_parent, color, {}, false, window);
|
||||
if (dialog.ShowModal() == wxID_OK && dialog.selection()) {
|
||||
const auto& result = std::get<ColorRGBA>(*dialog.selection());
|
||||
apply_user_color(wxColour(static_cast<unsigned char>(std::lround(result.r() * 255.f)),
|
||||
static_cast<unsigned char>(std::lround(result.g() * 255.f)),
|
||||
static_cast<unsigned char>(std::lround(result.b() * 255.f))));
|
||||
void ColourPicker::on_button_click(wxCommandEvent &event) {
|
||||
#if !defined(__linux__) && !defined(__LINUX__)
|
||||
if (m_clrData) {
|
||||
std::vector<std::string> colors = wxGetApp().app_config->get_custom_color_from_config();
|
||||
for (int i = 0; i < colors.size(); i++) {
|
||||
m_clrData->SetCustomColour(i, string_to_wxColor(colors[i]));
|
||||
}
|
||||
}
|
||||
m_picker_widget->OnButtonClick(event);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColourPicker::convert_to_picker_widget(wxColourPickerCtrl *widget)
|
||||
{
|
||||
#if !defined(__linux__) && !defined(__LINUX__)
|
||||
m_picker_widget = dynamic_cast<wxColourPickerWidget*>(widget->GetPickerCtrl());
|
||||
if (m_picker_widget) {
|
||||
m_picker_widget->Bind(wxEVT_BUTTON, &ColourPicker::on_button_click, this);
|
||||
m_clrData = m_picker_widget->GetColourData();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColourPicker::save_colors_to_config() {
|
||||
#if !defined(__linux__) && !defined(__LINUX__)
|
||||
if (m_clrData) {
|
||||
std::vector<std::string> colors;
|
||||
if (colors.size() != CUSTOM_COLOR_COUNT) {
|
||||
colors.resize(CUSTOM_COLOR_COUNT);
|
||||
}
|
||||
for (int i = 0; i < CUSTOM_COLOR_COUNT; i++) {
|
||||
colors[i] = color_to_string(m_clrData->GetCustomColour(i));
|
||||
}
|
||||
wxGetApp().app_config->save_custom_color_to_config(colors);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void PointCtrl::BUILD()
|
||||
|
||||
+24
-10
@@ -22,10 +22,10 @@
|
||||
#include <boost/any.hpp>
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include <wx/colour.h>
|
||||
#include <wx/colourdata.h>
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/bmpcbox.h>
|
||||
#include <optional>
|
||||
#include <wx/clrpicker.h>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Config.hpp"
|
||||
@@ -610,27 +610,41 @@ private:
|
||||
};
|
||||
|
||||
class ColourPicker : public Field {
|
||||
using Field::Field;
|
||||
|
||||
void set_undef_value(wxColourPickerCtrl* field);
|
||||
void draw_bmp_btn(wxColourPickerCtrl* field, wxColour color);
|
||||
public:
|
||||
ColourPicker(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
ColourPicker(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~ColourPicker() {}
|
||||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
void set_value(const std::string& value, bool change_event = false);
|
||||
|
||||
void set_value(const std::string& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(const boost::any& value, bool change_event = false) override;
|
||||
boost::any& get_value() override;
|
||||
void msw_rescale() override;
|
||||
void sys_color_changed() override;
|
||||
void enable() override { if (window) window->Enable(); }
|
||||
void disable() override{ if (window) window->Disable(); }
|
||||
|
||||
void enable() override { dynamic_cast<wxColourPickerCtrl*>(window)->Enable(); }
|
||||
void disable() override{ dynamic_cast<wxColourPickerCtrl*>(window)->Disable(); }
|
||||
wxWindow* getWindow() override { return window; }
|
||||
|
||||
private:
|
||||
// An absent result is cancellation; transparent color is the undefined field.
|
||||
void apply_user_color(const std::optional<wxColour>& color);
|
||||
void draw_bmp_btn();
|
||||
void on_button_click(wxCommandEvent& event);
|
||||
wxColour m_colour = wxTransparentColour;
|
||||
void convert_to_picker_widget(wxColourPickerCtrl *widget);
|
||||
void on_button_click(wxCommandEvent &WXUNUSED(ev));
|
||||
void save_colors_to_config();
|
||||
private:
|
||||
#if !defined(__linux__) && !defined(__LINUX__)
|
||||
wxColourData* m_clrData{nullptr};
|
||||
wxColourPickerWidget* m_picker_widget{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
class PointCtrl : public Field {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "EncodedFilament.hpp"
|
||||
#include "FilamentBitmapUtils.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GuiColor.hpp"
|
||||
@@ -277,6 +278,19 @@ static wxBitmap create_transparent_bitmap(const wxSize& size) {
|
||||
return bdc.bitmap;
|
||||
}
|
||||
|
||||
// Sort colors by HSV values (primarily by hue, then saturation, then value)
|
||||
static void sort_colors_by_hsv(std::vector<wxColour>& colors) {
|
||||
if (colors.size() < 2) return;
|
||||
std::sort(colors.begin(), colors.end(),
|
||||
[](const wxColour& a, const wxColour& b) {
|
||||
ColourHSV ha = wxColourToHSV(a);
|
||||
ColourHSV hb = wxColourToHSV(b);
|
||||
if (ha.h != hb.h) return ha.h < hb.h;
|
||||
if (ha.s != hb.s) return ha.s < hb.s;
|
||||
return ha.v < hb.v;
|
||||
});
|
||||
}
|
||||
|
||||
static wxBitmap create_single_filament_bitmap(const wxColour& color, const wxSize& size)
|
||||
{
|
||||
// Check if color is transparent
|
||||
@@ -416,16 +430,24 @@ wxBitmap create_filament_bitmap(const std::vector<wxColour>& colors, const wxSiz
|
||||
{
|
||||
if (colors.empty()) return wxNullBitmap;
|
||||
|
||||
if (force_gradient && colors.size() >= 2) {
|
||||
return create_gradient_filament_bitmap(colors, size);
|
||||
// Make a copy to sort without modifying original
|
||||
std::vector<wxColour> sorted_colors = colors;
|
||||
|
||||
// Sort colors by HSV when there are 2 or more colors
|
||||
if (sorted_colors.size() >= 2) {
|
||||
sort_colors_by_hsv(sorted_colors);
|
||||
}
|
||||
|
||||
switch (colors.size()) {
|
||||
case 1: return create_single_filament_bitmap(colors[0], size);
|
||||
case 2: return create_dual_filament_bitmap(colors[0], colors[1], size);
|
||||
case 3: return create_triple_filament_bitmap(colors, size);
|
||||
case 4: return create_quadruple_filament_bitmap(colors, size);
|
||||
default: return create_gradient_filament_bitmap(colors, size);
|
||||
if (force_gradient && sorted_colors.size() >= 2) {
|
||||
return create_gradient_filament_bitmap(sorted_colors, size);
|
||||
}
|
||||
|
||||
switch (sorted_colors.size()) {
|
||||
case 1: return create_single_filament_bitmap(sorted_colors[0], size);
|
||||
case 2: return create_dual_filament_bitmap(sorted_colors[0], sorted_colors[1], size);
|
||||
case 3: return create_triple_filament_bitmap(sorted_colors, size);
|
||||
case 4: return create_quadruple_filament_bitmap(sorted_colors, size);
|
||||
default: return create_gradient_filament_bitmap(sorted_colors, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
#include "FilamentPickerDialog.hpp"
|
||||
#include "ColorPickerDialog.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include "GUI.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
@@ -83,8 +79,7 @@ void FilamentPickerDialog::StartFlashing()
|
||||
m_flash_timer->Start(50);
|
||||
}
|
||||
|
||||
FilamentPickerDialog::FilamentPickerDialog(wxWindow *parent, const wxString& fila_id, const FilamentColor& fila_color, const std::string& fila_type,
|
||||
std::optional<FilamentColorPickerValue> ordered_color)
|
||||
FilamentPickerDialog::FilamentPickerDialog(wxWindow *parent, const wxString& fila_id, const FilamentColor& fila_color, const std::string& fila_type)
|
||||
: DPIDialog(parent ? parent : wxGetApp().mainframe,
|
||||
wxID_ANY,
|
||||
_L("Select Filament"),
|
||||
@@ -97,7 +92,6 @@ FilamentPickerDialog::FilamentPickerDialog(wxWindow *parent, const wxString& fil
|
||||
m_color_query = new FilamentColorCodeQuery();
|
||||
m_is_data_loaded = LoadFilamentData(fila_id);
|
||||
m_cur_filament_color = fila_color;
|
||||
m_ordered_color = std::move(ordered_color);
|
||||
wxString color_name = m_color_query->GetFilaColorName(fila_id, fila_color);
|
||||
m_cur_color_name = new wxString(color_name);
|
||||
|
||||
@@ -447,9 +441,6 @@ wxScrolledWindow* FilamentPickerDialog::CreateColorGrid()
|
||||
|
||||
// Bind click
|
||||
btn->Bind(wxEVT_LEFT_DOWN, [this, btn, color_code](wxMouseEvent& evt) {
|
||||
m_selection_changed = true;
|
||||
m_ordered_color.reset();
|
||||
m_custom_selection.reset();
|
||||
m_cur_filament_color = color_code->GetFilaColor();
|
||||
UpdatePreview(*color_code);
|
||||
UpdateButtonStates(btn);
|
||||
@@ -520,27 +511,24 @@ void FilamentPickerDialog::UpdatePreview(const FilamentColorCode& color_code)
|
||||
Layout();
|
||||
}
|
||||
|
||||
void FilamentPickerDialog::UpdateCustomColorPreview(const std::vector<wxColour>& wx_colors, bool gradient)
|
||||
void FilamentPickerDialog::UpdateCustomColorPreview(const wxColour& custom_color)
|
||||
{
|
||||
std::vector<wxColour> wx_colors = {custom_color};
|
||||
|
||||
// Update preview bitmap
|
||||
wxBitmap bmp = create_filament_bitmap(wx_colors, COLOR_DEMO_SIZE, gradient);
|
||||
wxBitmap bmp = create_filament_bitmap(wx_colors, COLOR_DEMO_SIZE, false);
|
||||
|
||||
if (bmp.IsOk()) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Custom color bitmap created successfully: " << bmp.GetWidth() << "x" << bmp.GetHeight();
|
||||
m_color_demo->SetBitmap(bmp);
|
||||
m_color_demo->SetBackgroundColour(wx_colors.front());
|
||||
m_color_demo->SetBackgroundColour(custom_color);
|
||||
m_color_demo->Refresh();
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to create custom color bitmap";
|
||||
}
|
||||
|
||||
// Update preview labels for custom color
|
||||
wxString label;
|
||||
for (const auto& color : wx_colors) {
|
||||
if (!label.empty()) label += " / ";
|
||||
label += color.GetAsString(wxC2S_HTML_SYNTAX);
|
||||
}
|
||||
m_label_preview_color->SetLabel(label);
|
||||
m_label_preview_color->SetLabel(custom_color.GetAsString(wxC2S_HTML_SYNTAX));
|
||||
m_label_preview_idx->SetLabel("");
|
||||
Layout();
|
||||
}
|
||||
@@ -569,6 +557,16 @@ void FilamentPickerDialog::CreateMoreInfoButton()
|
||||
m_more_btn->SetStyle(ButtonStyle::Regular, ButtonType::Expanded);
|
||||
}
|
||||
|
||||
wxColourData FilamentPickerDialog::GetSingleColorData()
|
||||
{
|
||||
wxColourData data;
|
||||
data.SetChooseFull(true);
|
||||
if (m_cur_filament_color.ColorCount() > 0) {
|
||||
data.SetColour(*m_cur_filament_color.m_colors.begin());
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
void FilamentPickerDialog::BindEvents()
|
||||
{
|
||||
// Bind mouse events for window dragging
|
||||
@@ -590,37 +588,27 @@ void FilamentPickerDialog::BindEvents()
|
||||
// Pause click detection while color picker is open
|
||||
StopClickDetection();
|
||||
|
||||
FilamentColorPickerValue current;
|
||||
if (m_ordered_color) {
|
||||
current = *m_ordered_color;
|
||||
} else {
|
||||
for (const auto& color : m_cur_filament_color.m_colors)
|
||||
current.colors.push_back(color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString());
|
||||
current.gradient = m_cur_filament_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR;
|
||||
}
|
||||
const auto initial = filament_color_picker_initial(current);
|
||||
std::optional<ColorSelection> selected;
|
||||
{
|
||||
ColorPickerDialog dialog(this, initial, {true, true}, !filament_color_picker_selection(current), m_more_btn);
|
||||
if (dialog.ShowModal() == wxID_OK)
|
||||
selected = dialog.selection();
|
||||
}
|
||||
StartClickDetection();
|
||||
if (!filament_color_picker_changed(initial, selected))
|
||||
return;
|
||||
wxColourData original_data = GetSingleColorData();
|
||||
wxColourData result = show_sys_picker_dialog(this, original_data);
|
||||
|
||||
m_custom_selection = selected;
|
||||
m_ordered_color = filament_color_picker_result(*selected);
|
||||
m_selection_changed = true;
|
||||
std::vector<wxColour> colors;
|
||||
m_cur_filament_color.m_colors.clear();
|
||||
for (const auto& color : m_ordered_color->colors) {
|
||||
colors.emplace_back(wxString::FromUTF8(color));
|
||||
m_cur_filament_color.m_colors.insert(colors.back());
|
||||
// Resume click detection after color picker closes
|
||||
StartClickDetection();
|
||||
|
||||
// Check if user actually selected a different color
|
||||
if (result.GetColour() != original_data.GetColour()) {
|
||||
wxColour selected_color = result.GetColour();
|
||||
|
||||
// Update m_current_filament_color with the selected color
|
||||
m_cur_filament_color.m_colors.clear();
|
||||
m_cur_filament_color.m_colors.insert(selected_color);
|
||||
m_cur_filament_color.m_color_type = FilamentColor::ColorType::SINGLE_CLR;
|
||||
|
||||
// Update preview
|
||||
UpdateCustomColorPreview(selected_color);
|
||||
|
||||
// Clear currently selected button since custom color selected
|
||||
UpdateButtonStates(nullptr);
|
||||
}
|
||||
m_cur_filament_color.m_color_type = m_ordered_color->gradient ? FilamentColor::ColorType::GRADIENT_CLR : FilamentColor::ColorType::SINGLE_CLR;
|
||||
UpdateCustomColorPreview(colors, m_ordered_color->gradient);
|
||||
UpdateButtonStates(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "FilamentBitmapUtils.hpp"
|
||||
#include "ColorPickerDialog.hpp"
|
||||
#include <optional>
|
||||
#include "Widgets/Button.hpp"
|
||||
#include "EncodedFilament.hpp"
|
||||
#include <wx/colourdata.h>
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/event.h>
|
||||
@@ -26,13 +25,10 @@ namespace Slic3r { namespace GUI {
|
||||
class FilamentPickerDialog : public DPIDialog
|
||||
{
|
||||
public:
|
||||
FilamentPickerDialog(wxWindow *parent, const wxString &fila_id, const FilamentColor &fila_color, const std::string &fila_type,
|
||||
std::optional<FilamentColorPickerValue> ordered_color = std::nullopt);
|
||||
FilamentPickerDialog(wxWindow *parent, const wxString &fila_id, const FilamentColor &fila_color, const std::string &fila_type);
|
||||
virtual ~FilamentPickerDialog();
|
||||
|
||||
// Public interface methods
|
||||
bool HasSelectionChanged() const { return m_selection_changed; }
|
||||
const std::optional<ColorSelection>& GetCustomColorSelection() const { return m_custom_selection; }
|
||||
bool IsDataLoaded() const { return m_is_data_loaded; }
|
||||
wxColour GetSelectedColour() const;
|
||||
const FilamentColor& GetSelectedFilamentColor() const { return m_cur_filament_color; }
|
||||
@@ -72,7 +68,7 @@ private:
|
||||
|
||||
// UI update methods
|
||||
void UpdatePreview(const FilamentColorCode& filament);
|
||||
void UpdateCustomColorPreview(const std::vector<wxColour>& colors, bool gradient);
|
||||
void UpdateCustomColorPreview(const wxColour& custom_color);
|
||||
void UpdateButtonStates(wxBitmapButton* selected_btn);
|
||||
|
||||
// Shaped window methods
|
||||
@@ -81,6 +77,7 @@ private:
|
||||
|
||||
// Data loading
|
||||
bool LoadFilamentData(const wxString& fila_id);
|
||||
wxColourData GetSingleColorData();
|
||||
|
||||
// Flash effect
|
||||
void StartFlashing();
|
||||
@@ -101,9 +98,6 @@ private:
|
||||
FilamentColorCodes* m_cur_color_codes{nullptr};
|
||||
wxBitmapButton* m_cur_selected_btn{nullptr};
|
||||
FilamentColor m_cur_filament_color;
|
||||
std::optional<FilamentColorPickerValue> m_ordered_color;
|
||||
std::optional<ColorSelection> m_custom_selection;
|
||||
bool m_selection_changed = false;
|
||||
|
||||
// Shaped window members
|
||||
wxBitmap m_shape_bmp;
|
||||
|
||||
@@ -816,18 +816,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
|
||||
bool MainFrame::handle_global_shortcut(const KeyChord& chord)
|
||||
{
|
||||
// ImGui draws its text fields (the text tool's box, a sketch value field) on the GL canvas, so
|
||||
// while one of them is typed into the focused wx window is that canvas or an in-canvas frame:
|
||||
// neither is a wxControl, so focus_keeps_space() cannot see it and CHAR_HOOK takes the chord
|
||||
// here first -- Space opened the Speed Dial, Ctrl+P the Preferences, and the field never saw
|
||||
// the key. ImGui raises WantCaptureKeyboard while one of its items owns the keyboard, so leave
|
||||
// the key to it then. A focused wxControl (text field, list, web view) is untouched: those
|
||||
// keep their own keys, and ImGui's flag can outlive the focus moving to one of them.
|
||||
wxWindow* const focus = wxWindow::FindFocus();
|
||||
ImGuiWrapper* const imgui = wxGetApp().imgui();
|
||||
if (focus != nullptr && imgui != nullptr && imgui->want_keyboard() && dynamic_cast<wxControl*>(focus) == nullptr)
|
||||
return false;
|
||||
|
||||
const std::optional<Shortcut> shortcut = wxGetApp().shortcuts().lookup(ShortcutContext::Global, chord);
|
||||
if (!shortcut.has_value())
|
||||
return false;
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
#include "slic3r/GUI/CalibrationWizardPage.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
@@ -36,12 +34,14 @@
|
||||
#include <wx/image.h>
|
||||
#include <wx/anybutton.h>
|
||||
#include <wx/app.h>
|
||||
#include <wx/colourdata.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/statbox.h>
|
||||
#include <wx/colordlg.h>
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/menu.h>
|
||||
#include <wx/odcombo.h>
|
||||
@@ -69,7 +69,6 @@
|
||||
#include "MsgDialog.hpp"
|
||||
#include "ParamsDialog.hpp"
|
||||
#include "FilamentPickerDialog.hpp"
|
||||
#include "ColorPickerDialog.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
#include "DeviceCore/DevManager.h"
|
||||
@@ -85,24 +84,6 @@
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
static FilamentColorPickerValue filament_color_picker_value(const DynamicPrintConfig& config, std::size_t index)
|
||||
{
|
||||
FilamentColorPickerValue value;
|
||||
if (const auto* primary = config.opt<ConfigOptionStrings>("filament_colour"); primary && index < primary->values.size())
|
||||
value.primary = primary->values[index];
|
||||
if (const auto* multi = config.opt<ConfigOptionStrings>("filament_multi_colour"); multi && index < multi->values.size()) {
|
||||
std::istringstream stream(multi->values[index]);
|
||||
for (std::string color; stream >> color;)
|
||||
value.colors.push_back(std::move(color));
|
||||
}
|
||||
if (value.colors.empty() && !value.primary.empty())
|
||||
value.colors.push_back(value.primary);
|
||||
if (const auto* types = config.opt<ConfigOptionStrings>("filament_colour_type"); types && index < types->values.size())
|
||||
value.gradient = value.colors.size() > 1 && types->values[index] == "0";
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
#define BORDER_W 10
|
||||
|
||||
// ---------------------------------
|
||||
@@ -930,37 +911,31 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
|
||||
FilamentColor fila_color = get_cur_color_info();
|
||||
|
||||
// Show filament picker dialog
|
||||
FilamentPickerDialog dialog(this, fila_id, fila_color, fila_type,
|
||||
filament_color_picker_value(m_preset_bundle->project_config, m_filament_idx));
|
||||
FilamentPickerDialog dialog(this, fila_id, fila_color, fila_type);
|
||||
|
||||
if (!dialog.IsDataLoaded()) {
|
||||
// If FilamentPicker fails, fallback to default color picker
|
||||
show_default_color_picker();
|
||||
} else if (dialog.ShowModal() == wxID_OK && dialog.HasSelectionChanged()) {
|
||||
if (const auto& custom = dialog.GetCustomColorSelection()) {
|
||||
const auto value = filament_color_picker_result(*custom);
|
||||
sync_colour_config(value.colors, value.gradient);
|
||||
} else if (dialog.ShowModal() == wxID_OK) {
|
||||
// Get selected filament color data
|
||||
FilamentColor fila_color = dialog.GetSelectedFilamentColor();
|
||||
|
||||
// Check if we have valid color data
|
||||
if (!fila_color.m_colors.empty()) {
|
||||
// Convert to storage format
|
||||
std::vector<std::string> colors;
|
||||
for (const wxColour& color : fila_color.m_colors) {
|
||||
colors.push_back(color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString());
|
||||
}
|
||||
|
||||
bool is_gradient = (fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR);
|
||||
this->sync_colour_config(colors, is_gradient);
|
||||
} else {
|
||||
// Get selected filament color data
|
||||
FilamentColor fila_color = dialog.GetSelectedFilamentColor();
|
||||
|
||||
// Check if we have valid color data
|
||||
if (!fila_color.m_colors.empty()) {
|
||||
// Convert to storage format
|
||||
std::vector<std::string> colors;
|
||||
for (const wxColour& color : fila_color.m_colors) {
|
||||
colors.push_back(color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString());
|
||||
}
|
||||
|
||||
bool is_gradient = (fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR);
|
||||
this->sync_colour_config(colors, is_gradient);
|
||||
} else {
|
||||
// Fallback to basic color if no FilamentColor data
|
||||
wxColour selected_color = dialog.GetSelectedColour();
|
||||
if (selected_color.IsOk()) {
|
||||
std::vector<std::string> color = {selected_color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString()};
|
||||
this->sync_colour_config(color, false);
|
||||
}
|
||||
// Fallback to basic color if no FilamentColor data
|
||||
wxColour selected_color = dialog.GetSelectedColour();
|
||||
if (selected_color.IsOk()) {
|
||||
std::vector<std::string> color = {selected_color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString()};
|
||||
this->sync_colour_config(color, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1116,8 +1091,30 @@ bool PlaterPresetComboBox::switch_to_tab()
|
||||
|
||||
void PlaterPresetComboBox::change_extruder_color()
|
||||
{
|
||||
// The Linux sidebar menu edits the same ordered project color as its swatch.
|
||||
show_default_color_picker();
|
||||
// get current color
|
||||
DynamicPrintConfig* cfg = &wxGetApp().preset_bundle->project_config;
|
||||
auto colors = static_cast<ConfigOptionStrings*>(cfg->option("filament_colour")->clone());
|
||||
wxColour clr(colors->values[m_filament_idx]);
|
||||
if (!clr.IsOk())
|
||||
clr = wxColour(0, 0, 0); // Don't set alfa to transparence
|
||||
|
||||
auto data = new wxColourData();
|
||||
data->SetChooseFull(1);
|
||||
data->SetColour(clr);
|
||||
|
||||
wxColourDialog dialog(this, data);
|
||||
dialog.CenterOnParent();
|
||||
if (dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
colors->values[m_filament_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
|
||||
|
||||
DynamicPrintConfig cfg_new = *cfg;
|
||||
cfg_new.set_key_value("filament_colour", colors);
|
||||
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new);
|
||||
this->update();
|
||||
wxGetApp().plater()->on_config_change(cfg_new);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaterPresetComboBox::show_add_menu()
|
||||
@@ -1611,12 +1608,19 @@ FilamentColor PlaterPresetComboBox::get_cur_color_info()
|
||||
|
||||
void PlaterPresetComboBox::show_default_color_picker()
|
||||
{
|
||||
const auto value = filament_color_picker_value(wxGetApp().preset_bundle->project_config, m_filament_idx);
|
||||
const auto initial = filament_color_picker_initial(value);
|
||||
ColorPickerDialog dialog(this, initial, {true, true}, !filament_color_picker_selection(value), clr_picker);
|
||||
if (dialog.ShowModal() == wxID_OK && filament_color_picker_changed(initial, dialog.selection())) {
|
||||
const auto selected = filament_color_picker_result(*dialog.selection());
|
||||
sync_colour_config(selected.colors, selected.gradient);
|
||||
DynamicPrintConfig* cfg = &wxGetApp().preset_bundle->project_config;
|
||||
auto colors = static_cast<ConfigOptionStrings*>(cfg->option("filament_colour")->clone());
|
||||
wxColour current_clr(colors->values[m_filament_idx]);
|
||||
if (!current_clr.IsOk())
|
||||
current_clr = wxColour(0, 0, 0); // Don't set alfa to transparence
|
||||
|
||||
m_clrData.SetColour(current_clr);
|
||||
|
||||
wxColourData data = show_sys_picker_dialog(this, m_clrData);
|
||||
if (m_clrData.GetColour() != data.GetColour()) {
|
||||
std::vector<std::string> color = {data.GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString()};
|
||||
m_clrData.SetColour(data.GetColour());
|
||||
sync_colour_config(color, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <wx/colourdata.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/gdicmn.h>
|
||||
#include <wx/clrpicker.h>
|
||||
@@ -200,6 +201,7 @@ public:
|
||||
|
||||
// BBS
|
||||
wxButton* clr_picker { nullptr };
|
||||
wxColourData m_clrData;
|
||||
|
||||
wxColor get_color() { return m_color; }
|
||||
|
||||
|
||||
@@ -10,11 +10,6 @@
|
||||
#include "GUI_App.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "ColorDecomposeDialog.hpp"
|
||||
#include "ColorPickerDialog.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include <wx/weakref.h>
|
||||
#include "ColorDecomposeSupport.hpp"
|
||||
#include "Widgets/StateColor.hpp"
|
||||
#include "Widgets/StaticLine.hpp"
|
||||
@@ -37,7 +32,9 @@
|
||||
#include <mutex>
|
||||
#include <wx/button.h>
|
||||
#include <wx/colour.h>
|
||||
#include <wx/colordlg.h>
|
||||
#include <wx/dc.h>
|
||||
#include <wx/colourdata.h>
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/dcbuffer.h>
|
||||
#include <wx/dialog.h>
|
||||
@@ -512,6 +509,30 @@ static wxString auto_mix_mode_label(TextureAutoMixMode mode)
|
||||
_L("One-click RYBW auto-mix");
|
||||
}
|
||||
|
||||
static wxPoint constrained_dialog_position(wxWindow* anchor, const wxSize& dialog_size)
|
||||
{
|
||||
if (!anchor)
|
||||
return wxDefaultPosition;
|
||||
|
||||
wxSize size = dialog_size;
|
||||
if (size.x <= 0 || size.y <= 0)
|
||||
size = wxSize(anchor->FromDIP(450), anchor->FromDIP(350));
|
||||
|
||||
wxPoint pos = anchor->ClientToScreen(wxPoint(0, anchor->GetSize().y));
|
||||
wxRect display_rect;
|
||||
int display_idx = wxDisplay::GetFromPoint(pos);
|
||||
if (display_idx != wxNOT_FOUND)
|
||||
display_rect = wxDisplay(display_idx).GetClientArea();
|
||||
else
|
||||
display_rect = wxDisplay().GetClientArea();
|
||||
|
||||
pos.x = std::clamp(pos.x, display_rect.GetLeft(),
|
||||
std::max(display_rect.GetLeft(), display_rect.GetRight() - size.x));
|
||||
pos.y = std::clamp(pos.y, display_rect.GetTop(),
|
||||
std::max(display_rect.GetTop(), display_rect.GetBottom() - size.y));
|
||||
return pos;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// FilamentSelectPopup
|
||||
// ============================================================
|
||||
@@ -632,22 +653,24 @@ public:
|
||||
auto on_add_filament = m_on_add_filament;
|
||||
wxWindow* popup_parent = GetParent();
|
||||
wxWindow* color_anchor = m_dialog_anchor ? m_dialog_anchor : popup_parent;
|
||||
wxWeakRef<FilamentSelectPopup> popup(this);
|
||||
m_closing_from_action = true;
|
||||
std::optional<wxColour> selected;
|
||||
{
|
||||
// Capture the stable mapping row before the transient popup is dismissed.
|
||||
ColorPickerDialog dlg(popup_parent, ColorRGBA(0.f, 0.f, 0.f, 1.f), {}, false, color_anchor);
|
||||
if (popup) popup->Dismiss();
|
||||
// Dismissal can delete the popup. Only copied locals are used below.
|
||||
if (dlg.ShowModal() == wxID_OK && dlg.selection()) {
|
||||
const auto& color = std::get<ColorRGBA>(*dlg.selection());
|
||||
selected = wxColour(static_cast<unsigned char>(std::lround(color.r() * 255.f)),
|
||||
static_cast<unsigned char>(std::lround(color.g() * 255.f)),
|
||||
static_cast<unsigned char>(std::lround(color.b() * 255.f)));
|
||||
}
|
||||
Dismiss();
|
||||
wxColourData cd;
|
||||
cd.SetChooseFull(true);
|
||||
wxColourDialog dlg(popup_parent, &cd);
|
||||
auto move_color_dialog = [&dlg, color_anchor]() {
|
||||
dlg.Move(constrained_dialog_position(color_anchor, dlg.GetBestSize()));
|
||||
};
|
||||
dlg.Bind(wxEVT_SHOW, [move_color_dialog](wxShowEvent& e) mutable {
|
||||
e.Skip();
|
||||
if (e.IsShown())
|
||||
move_color_dialog();
|
||||
});
|
||||
move_color_dialog();
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
wxColour clr = dlg.GetColourData().GetColour();
|
||||
if (on_add_filament) on_add_filament(clr);
|
||||
}
|
||||
if (selected && on_add_filament) on_add_filament(*selected);
|
||||
});
|
||||
|
||||
m_content->SetSizer(outer);
|
||||
|
||||
@@ -38,8 +38,6 @@
|
||||
#include <cstddef>
|
||||
#include <wx/simplebook.h>
|
||||
#include <wx/dcgraph.h>
|
||||
#include <wx/graphics.h>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <wx/timer.h>
|
||||
@@ -1411,17 +1409,22 @@ void AMSLib::render_lite_lib(wxDC& dc)
|
||||
|
||||
dc.SetPen(wxPen(*wxTRANSPARENT_PEN));
|
||||
if (m_info.material_cols.size() > 1) {
|
||||
wxRect color_rect(FromDIP(10), FromDIP(10), libsize.x - FromDIP(18), libsize.y - FromDIP(18));
|
||||
int left = FromDIP(10);
|
||||
int gwidth = std::round(libsize.x / (m_info.material_cols.size() - 1));
|
||||
//gradient
|
||||
if (m_info.ctype == 0) {
|
||||
fill_gradient_rect_east(dc, color_rect, m_info.material_cols.front(), m_info.material_cols.back());
|
||||
for (int i = 0; i < m_info.material_cols.size() - 1; i++) {
|
||||
auto rect = wxRect(left, FromDIP(10), libsize.x - FromDIP(18), libsize.y - FromDIP(18));
|
||||
fill_gradient_rect_east(dc, rect, m_info.material_cols[i], m_info.material_cols[i + 1]);
|
||||
left += gwidth;
|
||||
}
|
||||
}
|
||||
else {
|
||||
int cols_size = m_info.material_cols.size();
|
||||
for (int i = 0; i < cols_size; i++) {
|
||||
dc.SetBrush(wxBrush(m_info.material_cols[i]));
|
||||
int x = color_rect.x + color_rect.width * i / cols_size;
|
||||
int next_x = color_rect.x + color_rect.width * (i + 1) / cols_size;
|
||||
dc.DrawRectangle(x, color_rect.y, next_x - x, color_rect.height);
|
||||
float x = FromDIP(10) + ((float)libsize.x - FromDIP(18)) * i / cols_size;
|
||||
dc.DrawRoundedRectangle(x, FromDIP(10), ((float)libsize.x - FromDIP(17)) / cols_size, libsize.y - FromDIP(20), 0);
|
||||
}
|
||||
dc.SetBrush(wxBrush(tmp_lib_colour));
|
||||
}
|
||||
@@ -1513,27 +1516,7 @@ void AMSLib::render_generic_lib(wxDC &dc)
|
||||
int top = height - curr_height;
|
||||
|
||||
if (m_ams_model == EXT_AMS){
|
||||
wxRect color_rect(FromDIP(1), FromDIP(1), size.x - FromDIP(2), size.y - FromDIP(1));
|
||||
if ((m_info.ctype == 0 || m_info.ctype == 1) && m_info.material_cols.size() > 1 && alpha != 0) {
|
||||
if (m_info.ctype == 0) {
|
||||
fill_gradient_rect_east(dc, color_rect, m_info.material_cols.front(), m_info.material_cols.back());
|
||||
}
|
||||
else {
|
||||
int cols_size = static_cast<int>(m_info.material_cols.size());
|
||||
for (int i = 0; i < cols_size; i++) {
|
||||
dc.SetPen(wxPen(*wxTRANSPARENT_PEN));
|
||||
dc.SetBrush(wxBrush(m_info.material_cols[i]));
|
||||
int x = color_rect.x + color_rect.width * i / cols_size;
|
||||
int next_x = color_rect.x + color_rect.width * (i + 1) / cols_size;
|
||||
dc.DrawRectangle(x, color_rect.y, next_x - x, color_rect.height);
|
||||
}
|
||||
}
|
||||
dc.SetPen(wxPen(*wxTRANSPARENT_PEN));
|
||||
dc.SetBrush(wxBrush(tmp_lib_colour));
|
||||
}
|
||||
else {
|
||||
dc.DrawRoundedRectangle(color_rect.x, color_rect.y, color_rect.width, color_rect.height, m_radius - 1);
|
||||
}
|
||||
dc.DrawRoundedRectangle(FromDIP(1), FromDIP(1), size.x - FromDIP(2), size.y - FromDIP(1), m_radius - 1);
|
||||
if (alpha == 0) {
|
||||
dc.DrawBitmap(m_bitmap_transparent_def.bmp(), FromDIP(2), FromDIP(2));
|
||||
}
|
||||
@@ -2861,38 +2844,30 @@ void AMSPreview::doRender(wxDC &dc)
|
||||
//dc.DrawRoundedRectangle((size.x - rec_size.x) / 2, (size.y - rec_size.y) / 2, rec_size.x, rec_size.y, FromDIP(2));
|
||||
if (iter.material_cols.size() > 1)
|
||||
{
|
||||
const wxSize swatch_size = m_ams_item_type == AMSModel::N3S_AMS ? AMS_ITEM_CUBE_SIZE : AMS_ITEM_CUBE_SIZE2;
|
||||
const wxRect rect((size.x - swatch_size.x) / 2, (size.y - swatch_size.y) / 2, swatch_size.x, swatch_size.y);
|
||||
const int radius = m_ams_item_type == AMSModel::N3S_AMS ? 0 : FromDIP(3);
|
||||
std::unique_ptr<wxGraphicsContext> owned_gc;
|
||||
wxGraphicsContext* gc = dc.GetGraphicsContext();
|
||||
if (!gc) {
|
||||
owned_gc.reset(wxGraphicsContext::CreateFromUnknownDC(dc));
|
||||
gc = owned_gc.get();
|
||||
}
|
||||
if (gc) {
|
||||
wxGraphicsGradientStops stops(iter.material_cols.front(), iter.material_cols.back());
|
||||
const size_t count = iter.material_cols.size();
|
||||
if (iter.ctype == 0) {
|
||||
for (size_t i = 1; i + 1 < count; ++i)
|
||||
stops.Add(iter.material_cols[i], static_cast<float>(i) / (count - 1));
|
||||
} else {
|
||||
// Two stops at each boundary keep multicolour bands distinct.
|
||||
for (size_t i = 1; i < count; ++i) {
|
||||
const float position = static_cast<float>(i) / count;
|
||||
stops.Add(iter.material_cols[i - 1], position);
|
||||
stops.Add(iter.material_cols[i], position);
|
||||
int fleft = (size.x - AMS_ITEM_CUBE_SIZE.x) / 2;
|
||||
|
||||
float total_width = AMS_ITEM_CUBE_SIZE.x;
|
||||
int gwidth = (total_width / (iter.material_cols.size()));
|
||||
if (iter.ctype == 0) {
|
||||
for (int i = 0; i < iter.material_cols.size() - 1; i++) {
|
||||
|
||||
if ((fleft + gwidth) > (AMS_ITEM_CUBE_SIZE.x)) {
|
||||
gwidth = (fleft + AMS_ITEM_CUBE_SIZE.x) - fleft;
|
||||
}
|
||||
|
||||
auto rect = wxRect(fleft, (size.y - AMS_ITEM_CUBE_SIZE.y) / 2, gwidth, AMS_ITEM_CUBE_SIZE.y);
|
||||
fill_gradient_rect_east(dc, rect, iter.material_cols[i], iter.material_cols[i + 1]);
|
||||
fleft += gwidth;
|
||||
}
|
||||
}
|
||||
else {
|
||||
int cols_size = iter.material_cols.size();
|
||||
for (int i = 0; i < cols_size; i++) {
|
||||
dc.SetPen(wxPen(*wxTRANSPARENT_PEN));
|
||||
dc.SetBrush(wxBrush(iter.material_cols[i]));
|
||||
float x = (size.x - AMS_ITEM_CUBE_SIZE.x) / 2 + total_width * i / cols_size;
|
||||
dc.DrawRectangle(x, (size.y - AMS_ITEM_CUBE_SIZE.y) / 2, total_width / cols_size, AMS_ITEM_CUBE_SIZE.y);
|
||||
}
|
||||
gc->PushState();
|
||||
gc->SetPen(*wxTRANSPARENT_PEN);
|
||||
gc->SetBrush(gc->CreateLinearGradientBrush(rect.x, rect.y, rect.x + rect.width, rect.y, stops));
|
||||
gc->DrawRoundedRectangle(rect.x, rect.y, rect.width, rect.height, radius);
|
||||
gc->PopState();
|
||||
} else {
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(iter.material_colour);
|
||||
dc.DrawRoundedRectangle(rect, radius);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/dcmemory.h>
|
||||
#include <wx/colour.h>
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/colourdata.h>
|
||||
#include <wx/anybutton.h>
|
||||
#include <wx/object.h>
|
||||
#include <wx/popupwin.h>
|
||||
@@ -630,6 +630,43 @@ std::vector<std::vector<std::string>> read_color_pack(std::vector<std::string> c
|
||||
return color_info;
|
||||
}
|
||||
|
||||
wxColourData show_sys_picker_dialog(wxWindow *parent, const wxColourData &clr_data)
|
||||
{
|
||||
wxColourData data = clr_data;
|
||||
data.SetChooseFull(true);
|
||||
|
||||
// Load custom colors from config (support both "r,g,b,a" and "#RRGGBB" formats)
|
||||
std::vector<std::string> colors = Slic3r::GUI::wxGetApp().app_config->get_custom_color_from_config();
|
||||
for (int i = 0; i < (int)colors.size(); i++) {
|
||||
wxColour c;
|
||||
if (colors[i].find(',') != std::string::npos)
|
||||
c = string_to_wxColor(colors[i]);
|
||||
else
|
||||
c = wxColour(colors[i]);
|
||||
if (c.IsOk())
|
||||
data.SetCustomColour(i, c);
|
||||
}
|
||||
|
||||
wxColourDialog dialog(parent, &data);
|
||||
dialog.SetTitle(_L("Please choose the filament color"));
|
||||
|
||||
if (dialog.ShowModal() == wxID_OK) {
|
||||
data = dialog.GetColourData();
|
||||
|
||||
// Save custom colors to config (use RGBA string format for consistency)
|
||||
std::vector<std::string> colors;
|
||||
colors.resize(CUSTOM_COLOR_COUNT);
|
||||
for (int i = 0; i < CUSTOM_COLOR_COUNT; i++) {
|
||||
wxColour custom_clr = data.GetCustomColour(i);
|
||||
if (custom_clr.IsOk())
|
||||
colors[i] = color_to_string(custom_clr);
|
||||
}
|
||||
Slic3r::GUI::wxGetApp().app_config->save_custom_color_to_config(colors);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
wxBitmap *get_extruder_color_icon(std::vector<std::string> colors, bool is_gradient, std::string label, int icon_width, int icon_height,
|
||||
const std::vector<wxColour> *ramp){
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <cstddef>
|
||||
#include <wx/checklst.h>
|
||||
#include <wx/colour.h>
|
||||
#include <wx/colourdata.h>
|
||||
#include <wx/combo.h>
|
||||
#include <wx/dataview.h>
|
||||
#include <wx/button.h>
|
||||
@@ -20,6 +21,7 @@
|
||||
#include <wx/scrolwin.h>
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/artprov.h>
|
||||
#include <wx/colordlg.h>
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
@@ -87,6 +89,7 @@ wxBitmap * get_extruder_color_icon(std::string color, std::string label, int ico
|
||||
wxBitmap * get_extruder_color_icon(std::vector<std::string> colors, bool is_gradient, std::string label, int icon_width, int icon_height,
|
||||
const std::vector<wxColour> *ramp = nullptr);
|
||||
std::vector<std::vector<std::string>> read_color_pack(std::vector<std::string> color_pack);
|
||||
wxColourData show_sys_picker_dialog(wxWindow *parent, const wxColourData &clr_data);
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
@@ -597,127 +596,3 @@ TEST_CASE("Body centering survives islands merging and splitting between layers"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Z contouring slices an object that has several instances", "[PrintObject]")
|
||||
{
|
||||
Print print;
|
||||
Model model;
|
||||
place_two_cube_instances_apart(10., { { "zaa_enabled", "1" } }, print, model);
|
||||
REQUIRE(print.objects().size() == 1);
|
||||
REQUIRE(print.objects().front()->instances().size() == 2);
|
||||
|
||||
CHECK_NOTHROW(print.process());
|
||||
CHECK(print.objects().front()->is_step_done(posContouring));
|
||||
}
|
||||
|
||||
TEST_CASE("Z contouring slices instances of one object that are turned differently", "[PrintObject]")
|
||||
{
|
||||
// An instance turned about Z is sliced as a PrintObject of its own, with the same ModelObject behind it.
|
||||
Print print;
|
||||
Model model;
|
||||
place_two_cube_instances_apart(10., { { "zaa_enabled", "1" } }, print, model);
|
||||
model.objects.front()->instances.back()->set_rotation(Z, PI / 4.);
|
||||
print.apply(model, DynamicPrintConfig(print.full_print_config()));
|
||||
REQUIRE(print.objects().size() == 2);
|
||||
|
||||
CHECK_NOTHROW(print.process());
|
||||
for (const PrintObject *object : print.objects())
|
||||
CHECK(object->is_step_done(posContouring));
|
||||
}
|
||||
|
||||
// A box tilted about X, so that its top slopes along Y and Z contouring has a surface to follow, with
|
||||
// one instance for each Z rotation given, laid out in a row.
|
||||
static void slice_tilted_box_instances(const std::vector<double> &z_rotations, Print &print, Model &model)
|
||||
{
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({ { "zaa_enabled", "1" } });
|
||||
|
||||
TriangleMesh box = make_cube(20, 20, 10);
|
||||
box.rotate_x(float(PI / 12.));
|
||||
ModelObject *object = model.add_object();
|
||||
object->add_volume(std::move(box));
|
||||
for (size_t idx = 0; idx < z_rotations.size(); ++idx) {
|
||||
ModelInstance *instance = object->add_instance();
|
||||
instance->set_offset(Vec3d(50. + 50. * double(idx), 100., 0.));
|
||||
instance->set_rotation(Z, z_rotations[idx]);
|
||||
}
|
||||
object->ensure_on_bed();
|
||||
print.auto_assign_extruders(object);
|
||||
print.apply(model, config);
|
||||
print.validate();
|
||||
print.set_status_silent();
|
||||
print.process();
|
||||
}
|
||||
|
||||
// The Z that contouring gave each point of the object's extrusions, by layer and by XY in the
|
||||
// PrintObject's own frame, which does not depend on where its instances stand on the bed.
|
||||
static std::vector<std::map<std::pair<coord_t, coord_t>, coord_t>> contoured_heights(const PrintObject &object)
|
||||
{
|
||||
std::vector<std::map<std::pair<coord_t, coord_t>, coord_t>> heights(object.layer_count());
|
||||
for (size_t layer_idx = 0; layer_idx < object.layer_count(); ++layer_idx) {
|
||||
auto &layer_heights = heights[layer_idx];
|
||||
std::function<void(const ExtrusionEntity &)> visit = [&](const ExtrusionEntity &entity) {
|
||||
if (const auto *path = dynamic_cast<const ExtrusionPath *>(&entity)) {
|
||||
if (path->z_contoured)
|
||||
for (const Point3 &pt : path->polyline.points)
|
||||
layer_heights[{ pt.x(), pt.y() }] = pt.z();
|
||||
} else if (const auto *multipath = dynamic_cast<const ExtrusionMultiPath *>(&entity)) {
|
||||
for (const ExtrusionPath &path : multipath->paths)
|
||||
visit(path);
|
||||
} else if (const auto *loop = dynamic_cast<const ExtrusionLoop *>(&entity)) {
|
||||
for (const ExtrusionPath &path : loop->paths)
|
||||
visit(path);
|
||||
} else if (const auto *collection = dynamic_cast<const ExtrusionEntityCollection *>(&entity)) {
|
||||
for (const ExtrusionEntity *child : collection->entities)
|
||||
visit(*child);
|
||||
}
|
||||
};
|
||||
for (const LayerRegion *region : object.get_layer(int(layer_idx))->regions()) {
|
||||
visit(region->perimeters);
|
||||
visit(region->fills);
|
||||
}
|
||||
}
|
||||
return heights;
|
||||
}
|
||||
|
||||
TEST_CASE("Z contouring follows the surface of each instance as it is turned", "[PrintObject]")
|
||||
{
|
||||
// Two instances of a box with a sloped top, the second turned a quarter turn about Z: its top slopes
|
||||
// along X instead of Y. Each is contoured against the box turned as that instance is, so it has to
|
||||
// match the same box placed alone with that turn.
|
||||
Print print;
|
||||
Model model;
|
||||
slice_tilted_box_instances({ 0., PI / 2. }, print, model);
|
||||
REQUIRE(print.objects().size() == 2);
|
||||
|
||||
for (const PrintObject *object : print.objects()) {
|
||||
const double z_rotation = object->instances().front().model_instance->get_rotation().z();
|
||||
Print reference;
|
||||
Model reference_model;
|
||||
slice_tilted_box_instances({ z_rotation }, reference, reference_model);
|
||||
REQUIRE(reference.objects().size() == 1);
|
||||
|
||||
const auto heights = contoured_heights(*object);
|
||||
const auto reference_heights = contoured_heights(*reference.objects().front());
|
||||
REQUIRE(heights.size() == reference_heights.size());
|
||||
|
||||
size_t matched = 0, lifted = 0;
|
||||
for (size_t layer_idx = 0; layer_idx < heights.size(); ++layer_idx) {
|
||||
const coord_t print_z = scaled(object->get_layer(int(layer_idx))->print_z);
|
||||
for (const auto &[xy, z] : heights[layer_idx]) {
|
||||
const auto it = reference_heights[layer_idx].find(xy);
|
||||
if (it == reference_heights[layer_idx].end())
|
||||
continue;
|
||||
INFO("layer " << layer_idx << ", rotation " << z_rotation);
|
||||
CHECK(std::abs(z - it->second) <= scaled(0.001));
|
||||
++matched;
|
||||
if (std::abs(z - print_z) > scaled(0.01))
|
||||
++lifted;
|
||||
}
|
||||
}
|
||||
INFO("rotation " << z_rotation);
|
||||
// Enough points in common to mean something, and contouring did move some of them.
|
||||
CHECK(matched > 100);
|
||||
CHECK(lifted > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_moonraker_discovery.cpp
|
||||
test_network_discovery.cpp
|
||||
test_filament_bitmap_utils.cpp
|
||||
test_colour_picker_field.cpp
|
||||
test_filament_color_picker.cpp
|
||||
test_device_progress.cpp
|
||||
test_device_manager_integration.cpp
|
||||
test_web_media_controller.cpp
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#include "slic3r/GUI/Field.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include <boost/any.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <string>
|
||||
#include <wx/colour.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
namespace {
|
||||
class TestColourPicker : public ColourPicker {
|
||||
public:
|
||||
TestColourPicker() : ColourPicker(ConfigOptionDef{}, "filament_colour") {}
|
||||
std::string value() { return boost::any_cast<std::string>(get_value()); }
|
||||
};
|
||||
}
|
||||
|
||||
TEST_CASE("Undefined color fields remain distinct from opaque black", "[ColourPickerField]")
|
||||
{
|
||||
TestColourPicker field;
|
||||
REQUIRE(field.value().empty());
|
||||
field.set_value(std::string("#000000"));
|
||||
REQUIRE(field.value() == "#000000");
|
||||
field.set_value(std::string("invalid color"));
|
||||
REQUIRE(field.value().empty());
|
||||
field.set_value(boost::any(wxString("#123456")));
|
||||
REQUIRE(field.value() == "#123456");
|
||||
field.set_value(std::string());
|
||||
REQUIRE(field.value().empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Programmatic color field assignment stays silent", "[ColourPickerField]")
|
||||
{
|
||||
TestColourPicker field;
|
||||
int changes = 0;
|
||||
field.m_on_change = [&](const std::string&, const boost::any&) { ++changes; };
|
||||
field.set_value(std::string("#123456"), false);
|
||||
field.set_value(boost::any(wxString("#654321")), true);
|
||||
REQUIRE(changes == 0);
|
||||
REQUIRE(field.value() == "#654321");
|
||||
field.m_disable_change_event = true;
|
||||
field.set_value(std::string("#FF0000"));
|
||||
REQUIRE(field.value() == "#FF0000");
|
||||
REQUIRE(changes == 0);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
|
||||
#include <slic3r/GUI/DeviceCore/DevManager.h>
|
||||
#include <slic3r/GUI/DeviceManager.hpp>
|
||||
@@ -17,7 +16,6 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace Slic3r;
|
||||
using json = nlohmann::json;
|
||||
@@ -51,21 +49,6 @@ public:
|
||||
|
||||
AgentInfo get_agent_info() override { return m_info; }
|
||||
|
||||
int send_message(std::string dev_id, std::string json_str, int, int) override
|
||||
{
|
||||
published_device = std::move(dev_id);
|
||||
published_message = json::parse(json_str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override
|
||||
{
|
||||
return send_message(std::move(dev_id), std::move(json_str), qos, flag);
|
||||
}
|
||||
|
||||
std::string published_device;
|
||||
json published_message;
|
||||
|
||||
private:
|
||||
AgentInfo m_info;
|
||||
};
|
||||
@@ -177,62 +160,3 @@ TEST_CASE("Device manager filters and rehomes devices by printer-agent ownership
|
||||
CHECK(manager.get_my_machine_list("integration-agent-a").empty());
|
||||
CHECK(manager.get_my_machine_list("integration-agent-b").count(machine.dev_id) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("AMS filament settings keep legacy single-color commands compatible", "[DeviceManager][AMSMaterialsSetting][integration]")
|
||||
{
|
||||
ScopedAppConfig app_config;
|
||||
auto agent = std::make_shared<TestPrinterAgent>("integration-agent");
|
||||
NetworkAgent network(nullptr, agent);
|
||||
DeviceManager manager(&network, false, &app_config.config);
|
||||
MachineObject object(&manager, &network, "Test printer", "integration-device", "192.0.2.10");
|
||||
object.dev_connection_type = GENERATE("lan", "cloud");
|
||||
|
||||
REQUIRE(object.command_ams_filament_settings(1, 2, "filament-id", "setting-id", "FF000080", "PLA", 190, 220) == 0);
|
||||
|
||||
const json& payload = agent->published_message.at("print");
|
||||
CHECK(agent->published_device == object.get_dev_id());
|
||||
CHECK(payload.at("command") == "ams_filament_setting");
|
||||
CHECK(payload.at("ams_id") == 1);
|
||||
CHECK(payload.at("slot_id") == 2);
|
||||
CHECK(payload.at("tray_color") == "FF000080");
|
||||
CHECK_FALSE(payload.contains("cols"));
|
||||
CHECK_FALSE(payload.contains("ctype"));
|
||||
}
|
||||
|
||||
TEST_CASE("AMS filament settings preserve ordered gradient and multicolor RGBA components", "[DeviceManager][AMSMaterialsSetting][integration]")
|
||||
{
|
||||
ScopedAppConfig app_config;
|
||||
auto agent = std::make_shared<TestPrinterAgent>("integration-agent");
|
||||
NetworkAgent network(nullptr, agent);
|
||||
DeviceManager manager(&network, false, &app_config.config);
|
||||
MachineObject object(&manager, &network, "Test printer", "integration-device", "192.0.2.10");
|
||||
object.dev_connection_type = GENERATE("lan", "cloud");
|
||||
const int color_type = GENERATE(0, 1);
|
||||
const std::vector<std::string> colors{"FF000080", "0000FF40", "00FF00FF"};
|
||||
|
||||
REQUIRE(object.command_ams_filament_settings(1, 2, "filament-id", "setting-id", colors.front(), "PLA", 190, 220,
|
||||
colors, color_type) == 0);
|
||||
|
||||
const json& payload = agent->published_message.at("print");
|
||||
CHECK(payload.at("tray_color") == colors.front());
|
||||
CHECK(payload.at("cols").get<std::vector<std::string>>() == colors);
|
||||
CHECK(payload.at("ctype") == color_type);
|
||||
}
|
||||
|
||||
TEST_CASE("AMS filament settings default a supplied single-color list to type two", "[DeviceManager][AMSMaterialsSetting][integration]")
|
||||
{
|
||||
ScopedAppConfig app_config;
|
||||
auto agent = std::make_shared<TestPrinterAgent>("integration-agent");
|
||||
NetworkAgent network(nullptr, agent);
|
||||
DeviceManager manager(&network, false, &app_config.config);
|
||||
MachineObject object(&manager, &network, "Test printer", "integration-device", "192.0.2.10");
|
||||
object.dev_connection_type = GENERATE("lan", "cloud");
|
||||
const std::vector<std::string> colors{"FF000080"};
|
||||
|
||||
REQUIRE(object.command_ams_filament_settings(1, 2, "filament-id", "setting-id", colors.front(), "PLA", 190, 220,
|
||||
colors) == 0);
|
||||
|
||||
const json& payload = agent->published_message.at("print");
|
||||
CHECK(payload.at("cols").get<std::vector<std::string>>() == colors);
|
||||
CHECK(payload.at("ctype") == 2);
|
||||
}
|
||||
|
||||
@@ -22,13 +22,6 @@
|
||||
#include <cmath>
|
||||
|
||||
#include <wx/colour.h>
|
||||
#ifdef __WXMSW__
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/image.h>
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#endif
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "libslic3r/FilamentMixer.hpp"
|
||||
@@ -267,71 +260,3 @@ TEST_CASE("sample_gradient_ramp returns nothing without a usable curve or step c
|
||||
curve.points = {{0.0, kGradientMaxRatio, NAN, NAN}, {1.0, kGradientMinRatio, NAN, NAN}};
|
||||
REQUIRE(Slic3r::GUI::sample_gradient_ramp(wxColour(255, 0, 0), wxColour(0, 0, 255), curve, 0).empty());
|
||||
}
|
||||
|
||||
// The headless GTK/macOS suite does not initialize GUI display/font stock objects.
|
||||
// Exercise native bitmap pixels on Windows without adding that dependency elsewhere.
|
||||
#ifdef __WXMSW__
|
||||
TEST_CASE("Filament gradient pixels follow both endpoint directions without changing input", "[FilamentBitmapUtils]")
|
||||
{
|
||||
const bool reverse = GENERATE(false, true);
|
||||
const std::vector<wxColour> colors = reverse ? std::vector<wxColour>{wxColour(0, 0, 255), wxColour(255, 0, 0)}
|
||||
: std::vector<wxColour>{wxColour(255, 0, 0), wxColour(0, 0, 255)};
|
||||
const auto original = colors;
|
||||
const wxBitmap bitmap = Slic3r::GUI::create_filament_bitmap(colors, wxSize(64, 16), true);
|
||||
REQUIRE(bitmap.IsOk());
|
||||
const wxImage image = bitmap.ConvertToImage();
|
||||
REQUIRE(image.IsOk());
|
||||
// Interior pixels avoid backend-specific rasterization at the gradient boundary.
|
||||
for (const auto& sample : {std::pair<int, wxColour>{2, colors.front()}, {61, colors.back()}}) {
|
||||
REQUIRE(std::abs(int(image.GetRed(sample.first, 8)) - int(sample.second.Red())) <= 16);
|
||||
REQUIRE(std::abs(int(image.GetBlue(sample.first, 8)) - int(sample.second.Blue())) <= 16);
|
||||
}
|
||||
REQUIRE(colors == original);
|
||||
}
|
||||
|
||||
TEST_CASE("Filament halves preserve the same caller color order as gradients", "[FilamentBitmapUtils]")
|
||||
{
|
||||
const bool reverse = GENERATE(false, true);
|
||||
const std::vector<wxColour> colors = reverse ? std::vector<wxColour>{wxColour(0, 0, 255), wxColour(255, 0, 0)}
|
||||
: std::vector<wxColour>{wxColour(255, 0, 0), wxColour(0, 0, 255)};
|
||||
const auto original = colors;
|
||||
const wxBitmap bitmap = Slic3r::GUI::create_filament_bitmap(colors, wxSize(64, 16), false);
|
||||
REQUIRE(bitmap.IsOk());
|
||||
const wxImage image = bitmap.ConvertToImage();
|
||||
REQUIRE(image.IsOk());
|
||||
require_same_rgb(wxColour(image.GetRed(16, 8), image.GetGreen(16, 8), image.GetBlue(16, 8)), colors.front());
|
||||
require_same_rgb(wxColour(image.GetRed(48, 8), image.GetGreen(48, 8), image.GetBlue(48, 8)), colors.back());
|
||||
REQUIRE(colors == original);
|
||||
}
|
||||
|
||||
TEST_CASE("Three color filament stripes retain caller order", "[FilamentBitmapUtils]")
|
||||
{
|
||||
const std::vector<wxColour> colors{wxColour(0, 0, 255), wxColour(0, 255, 0), wxColour(255, 0, 0)};
|
||||
const auto original = colors;
|
||||
const wxBitmap bitmap = Slic3r::GUI::create_filament_bitmap(colors, wxSize(72, 24), false);
|
||||
REQUIRE(bitmap.IsOk());
|
||||
const wxImage image = bitmap.ConvertToImage();
|
||||
REQUIRE(image.IsOk());
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const int x = 12 + i * 24;
|
||||
require_same_rgb(wxColour(image.GetRed(x, 12), image.GetGreen(x, 12), image.GetBlue(x, 12)), colors[i]);
|
||||
}
|
||||
REQUIRE(colors == original);
|
||||
}
|
||||
|
||||
TEST_CASE("Four color filament quadrants retain caller order", "[FilamentBitmapUtils]")
|
||||
{
|
||||
const std::vector<wxColour> colors{wxColour(0, 0, 255), wxColour(0, 255, 0), wxColour(255, 0, 0), wxColour(255, 255, 0)};
|
||||
const auto original = colors;
|
||||
const wxBitmap bitmap = Slic3r::GUI::create_filament_bitmap(colors, wxSize(64, 32), false);
|
||||
REQUIRE(bitmap.IsOk());
|
||||
const wxImage image = bitmap.ConvertToImage();
|
||||
REQUIRE(image.IsOk());
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const int x = i % 2 ? 48 : 16, y = i / 2 ? 24 : 8;
|
||||
require_same_rgb(wxColour(image.GetRed(x, y), image.GetGreen(x, y), image.GetBlue(x, y)), colors[i]);
|
||||
}
|
||||
REQUIRE(colors == original);
|
||||
}
|
||||
|
||||
#endif // __WXMSW__
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
#include "slic3r/GUI/ColorPickerDialog.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
// One color more than an ordered gradient may hold.
|
||||
static std::vector<std::string> too_many_gradient_colors()
|
||||
{
|
||||
return std::vector<std::string>(COLOR_PICKER_MAX_GRADIENT_COLORS + 1, "#FF0000");
|
||||
}
|
||||
|
||||
TEST_CASE("Project gradient direction survives editing and RGB serialization", "[FilamentColorPicker]")
|
||||
{
|
||||
const std::vector<std::string> endpoints = {"#FF0000", "#0000FF"};
|
||||
const FilamentColorPickerValue stored{endpoints, true};
|
||||
const auto initial = filament_color_picker_selection(stored);
|
||||
REQUIRE(initial);
|
||||
REQUIRE(filament_color_picker_result(*initial).colors == endpoints);
|
||||
REQUIRE_FALSE(filament_color_picker_changed(*initial, initial));
|
||||
REQUIRE_FALSE(filament_color_picker_changed(*initial, std::nullopt));
|
||||
const auto reverse = filament_color_picker_selection({{endpoints[1], endpoints[0]}, true});
|
||||
REQUIRE(filament_color_picker_changed(*initial, reverse));
|
||||
REQUIRE(filament_color_picker_result(*reverse).colors.front() == endpoints.back());
|
||||
}
|
||||
|
||||
TEST_CASE("Unrepresentable project colors remain unchanged until a different selection is confirmed", "[FilamentColorPicker]")
|
||||
{
|
||||
const auto value = GENERATE(FilamentColorPickerValue{too_many_gradient_colors(), true},
|
||||
FilamentColorPickerValue{{"#FF0000", "#0000FF"}, false});
|
||||
REQUIRE_FALSE(filament_color_picker_selection(value));
|
||||
const auto seed = filament_color_picker_initial(value);
|
||||
REQUIRE_FALSE(filament_color_picker_changed(seed, seed));
|
||||
REQUIRE_FALSE(filament_color_picker_changed(seed, std::nullopt));
|
||||
const auto changed = filament_color_picker_selection({{"#FE0000"}, false});
|
||||
REQUIRE(filament_color_picker_changed(seed, changed));
|
||||
REQUIRE(filament_color_picker_result(*changed).colors == std::vector<std::string>{"#FE0000"});
|
||||
}
|
||||
|
||||
TEST_CASE("Filament seeds preserve primary alpha when colors are invalid or absent", "[FilamentColorPicker]")
|
||||
{
|
||||
const FilamentColorPickerValue stale{{"invalid", "#00FF00"}, false, "#12345680"};
|
||||
REQUIRE_FALSE(filament_color_picker_selection(stale));
|
||||
REQUIRE(filament_color_picker_result(filament_color_picker_initial(stale)).colors == std::vector<std::string>{"#12345680"});
|
||||
const auto value = filament_color_picker_selection({{"#12345680", "#ABCDEF01"}, true});
|
||||
REQUIRE(value);
|
||||
REQUIRE(filament_color_picker_result(*value).colors == std::vector<std::string>{"#12345680", "#ABCDEF01"});
|
||||
const auto missing = filament_color_picker_initial({});
|
||||
REQUIRE(filament_color_picker_result(missing).colors == std::vector<std::string>{"#000000"});
|
||||
}
|
||||
|
||||
TEST_CASE("Filament project colors round trip every alpha boundary with compatible opaque spelling", "[FilamentColorPicker]")
|
||||
{
|
||||
const auto hex = GENERATE(std::string("#12345600"), std::string("#12345601"),
|
||||
std::string("#12345680"), std::string("#123456FE"),
|
||||
std::string("#123456FF"), std::string("#123456"));
|
||||
const FilamentColorPickerValue stored{{hex}, false};
|
||||
const auto selection = filament_color_picker_selection(stored);
|
||||
REQUIRE(selection);
|
||||
REQUIRE_FALSE(filament_color_picker_changed(*selection, selection));
|
||||
REQUIRE_FALSE(filament_color_picker_changed(*selection, std::nullopt));
|
||||
const auto result = filament_color_picker_result(*selection);
|
||||
const std::string expected = hex == "#123456FF" ? "#123456" : hex;
|
||||
REQUIRE(result.colors == std::vector<std::string>{expected});
|
||||
ColorRGBA decoded;
|
||||
REQUIRE(decode_color(result.colors.front(), decoded));
|
||||
REQUIRE(filament_color_picker_result(ColorSelection{decoded}).colors == result.colors);
|
||||
const auto reloaded = filament_color_picker_selection(result);
|
||||
REQUIRE(reloaded);
|
||||
REQUIRE(filament_color_picker_result(*reloaded).colors == result.colors);
|
||||
}
|
||||
|
||||
TEST_CASE("Alpha only filament edits preserve ordered independent gradient endpoint alpha", "[FilamentColorPicker]")
|
||||
{
|
||||
const auto original = filament_color_picker_selection({{"#FF000000", "#0000FF80"}, true});
|
||||
const auto edited = filament_color_picker_selection({{"#FF000001", "#0000FFFE"}, true});
|
||||
REQUIRE(original);
|
||||
REQUIRE(edited);
|
||||
REQUIRE(filament_color_picker_changed(*original, edited));
|
||||
const auto result = filament_color_picker_result(*edited);
|
||||
REQUIRE(result.gradient);
|
||||
REQUIRE(result.colors == std::vector<std::string>{"#FF000001", "#0000FFFE"});
|
||||
const auto reloaded = filament_color_picker_selection(result);
|
||||
REQUIRE(reloaded);
|
||||
REQUIRE_FALSE(filament_color_picker_changed(*edited, reloaded));
|
||||
const auto reversed = filament_color_picker_selection({{result.colors[1], result.colors[0]}, true});
|
||||
REQUIRE(filament_color_picker_changed(*edited, reversed));
|
||||
const auto solid = filament_color_picker_selection({{"#12345680"}, false});
|
||||
const auto alpha_only = filament_color_picker_selection({{"#12345681"}, false});
|
||||
REQUIRE(solid);
|
||||
REQUIRE(alpha_only);
|
||||
REQUIRE(filament_color_picker_changed(*solid, alpha_only));
|
||||
REQUIRE(filament_color_picker_result(*alpha_only).colors == std::vector<std::string>{"#12345681"});
|
||||
const auto mixed = filament_color_picker_selection({{"#FF0000FF", "#0000FF00"}, true});
|
||||
REQUIRE(mixed);
|
||||
REQUIRE(filament_color_picker_result(*mixed).colors == std::vector<std::string>{"#FF0000", "#0000FF00"});
|
||||
}
|
||||
|
||||
TEST_CASE("Unrepresentable filament seeds retain first endpoint or valid primary alpha", "[FilamentColorPicker]")
|
||||
{
|
||||
auto too_many = too_many_gradient_colors();
|
||||
too_many.front() = "#FF000000";
|
||||
const auto value = GENERATE_COPY(FilamentColorPickerValue{too_many, true},
|
||||
FilamentColorPickerValue{{"#FF000000", "#0000FF80"}, false},
|
||||
FilamentColorPickerValue{{"invalid", "#FF0000"}, true, "#FF000000"});
|
||||
REQUIRE_FALSE(filament_color_picker_selection(value));
|
||||
const auto initial = filament_color_picker_initial(value);
|
||||
REQUIRE(filament_color_picker_result(initial).colors == std::vector<std::string>{"#FF000000"});
|
||||
REQUIRE_FALSE(filament_color_picker_changed(initial, initial));
|
||||
REQUIRE_FALSE(filament_color_picker_changed(initial, std::nullopt));
|
||||
const auto edited = filament_color_picker_selection({{"#FF000001"}, false});
|
||||
REQUIRE(filament_color_picker_changed(initial, edited));
|
||||
}
|
||||
|
||||
TEST_CASE("Ordered multi-color gradients round trip up to the color limit", "[FilamentColorPicker]")
|
||||
{
|
||||
const std::vector<std::string> all = {"#FF0000", "#FFFF0080", "#00FF00", "#00FFFF01", "#0000FF", "#FF00FF00", "#123456"};
|
||||
for (std::size_t count = 2; count <= std::min(all.size(), COLOR_PICKER_MAX_GRADIENT_COLORS); ++count) {
|
||||
const std::vector<std::string> colors(all.begin(), all.begin() + count);
|
||||
const auto selection = filament_color_picker_selection({colors, true});
|
||||
REQUIRE(selection);
|
||||
const auto result = filament_color_picker_result(*selection);
|
||||
REQUIRE(result.gradient);
|
||||
REQUIRE(result.colors == colors);
|
||||
REQUIRE_FALSE(filament_color_picker_changed(*selection, filament_color_picker_selection(result)));
|
||||
std::vector<std::string> reordered = colors;
|
||||
std::swap(reordered.front(), reordered.back());
|
||||
REQUIRE(filament_color_picker_changed(*selection, filament_color_picker_selection({reordered, true})));
|
||||
}
|
||||
REQUIRE_FALSE(filament_color_picker_selection({too_many_gradient_colors(), true}));
|
||||
}
|
||||
|
||||
TEST_CASE("Filament selections require complete hexadecimal colors and exact endpoint counts", "[FilamentColorPicker]")
|
||||
{
|
||||
const std::string hex = GENERATE("", "112233", "#ABC", "#1234567", "#123456789", "#GG2233", "#11223Z", "#112233G0", " #112233", "#112233 ");
|
||||
REQUIRE_FALSE(filament_color_picker_selection({{hex}, false}));
|
||||
REQUIRE_FALSE(filament_color_picker_selection({{"#112233", hex}, true}));
|
||||
const auto malformed = GENERATE(FilamentColorPickerValue{{}, false},
|
||||
FilamentColorPickerValue{{}, true},
|
||||
FilamentColorPickerValue{{"#112233"}, true});
|
||||
REQUIRE_FALSE(filament_color_picker_selection(malformed));
|
||||
}
|
||||
|
||||
TEST_CASE("Every RGBA byte survives the public filament conversion round trip", "[FilamentColorPicker]")
|
||||
{
|
||||
constexpr char digits[] = "0123456789ABCDEF";
|
||||
for (int byte = 0; byte < 256; ++byte) {
|
||||
std::string hex = "#";
|
||||
for (int channel = 0; channel < 4; ++channel) {
|
||||
hex += digits[byte >> 4];
|
||||
hex += digits[byte & 15];
|
||||
}
|
||||
const auto selection = filament_color_picker_selection({{hex}, false});
|
||||
REQUIRE(selection);
|
||||
const auto result = filament_color_picker_result(*selection);
|
||||
REQUIRE(result.colors == std::vector<std::string>{byte == 255 ? hex.substr(0, 7) : hex});
|
||||
REQUIRE_FALSE(filament_color_picker_changed(*selection, filament_color_picker_selection(result)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Filament serialization rejects invalid native RGBA channels", "[FilamentColorPicker]")
|
||||
{
|
||||
const float invalid = GENERATE(-0.1f, 1.1f, std::numeric_limits<float>::infinity(), std::numeric_limits<float>::quiet_NaN());
|
||||
const int channel = GENERATE(0, 1, 2, 3);
|
||||
ColorRGBA color;
|
||||
color[channel] = invalid;
|
||||
REQUIRE_THROWS_AS(filament_color_picker_result(color), std::invalid_argument);
|
||||
REQUIRE_THROWS_AS(filament_color_picker_result(ColorGradient{ColorRGBA::WHITE(), color}), std::invalid_argument);
|
||||
}
|
||||
Reference in New Issue
Block a user