diff --git a/common/app/common/geom/point.cljc b/common/app/common/geom/point.cljc
index 54f3f59f4..65b453b56 100644
--- a/common/app/common/geom/point.cljc
+++ b/common/app/common/geom/point.cljc
@@ -211,6 +211,7 @@
(unit (point y (- x))))
(defn point-line-distance
+ "Returns the distance from a point to a line defined by two points"
[point line-point1 line-point2]
(let [{x0 :x y0 :y} point
{x1 :x y1 :y} line-point1
diff --git a/common/app/common/math.cljc b/common/app/common/math.cljc
index d49bcf42c..9125c7c35 100644
--- a/common/app/common/math.cljc
+++ b/common/app/common/math.cljc
@@ -12,6 +12,10 @@
#?(:cljs
(:require [goog.math :as math])))
+(def PI
+ #?(:cljs (.-PI js/Math)
+ :clj Math/PI))
+
(defn nan?
[v]
#?(:cljs (js/isNaN v)
diff --git a/frontend/resources/images/icons/picker-harmony.svg b/frontend/resources/images/icons/picker-harmony.svg
new file mode 100644
index 000000000..c108e2812
--- /dev/null
+++ b/frontend/resources/images/icons/picker-harmony.svg
@@ -0,0 +1,2 @@
+
diff --git a/frontend/resources/images/icons/picker-hsv.svg b/frontend/resources/images/icons/picker-hsv.svg
new file mode 100644
index 000000000..2218c82a1
--- /dev/null
+++ b/frontend/resources/images/icons/picker-hsv.svg
@@ -0,0 +1 @@
+
diff --git a/frontend/resources/images/icons/picker-ramp.svg b/frontend/resources/images/icons/picker-ramp.svg
new file mode 100644
index 000000000..0e078a017
--- /dev/null
+++ b/frontend/resources/images/icons/picker-ramp.svg
@@ -0,0 +1 @@
+
diff --git a/frontend/resources/images/icons/picker.svg b/frontend/resources/images/icons/picker.svg
index f486028b4..be86a1808 100644
--- a/frontend/resources/images/icons/picker.svg
+++ b/frontend/resources/images/icons/picker.svg
@@ -1 +1,4 @@
-
\ No newline at end of file
+
diff --git a/frontend/resources/styles/main/partials/colorpicker.scss b/frontend/resources/styles/main/partials/colorpicker.scss
index 2ff6badc4..9b3d268e4 100644
--- a/frontend/resources/styles/main/partials/colorpicker.scss
+++ b/frontend/resources/styles/main/partials/colorpicker.scss
@@ -6,257 +6,487 @@
// Copyright (c) 2015-2016 Juan de la Cruz
.colorpicker {
+ box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
+ background-color: $color-white;
+}
+
+.colorpicker-content {
+ display: flex;
+ flex-direction: column;
+ padding: 0.5rem;
+
+ & > * {
+ width: 200px;
+ }
+
+ .top-actions {
display: flex;
- flex-direction: column;
- padding: 0.5rem;
- background-color: $color-white;
- box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
+ margin-bottom: 0.25rem;
+ justify-content: space-between;
- & > * {
- width: 200px;
+ .picker-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+
+ &.active,
+ &:hover svg {
+ fill: $color-primary;
+ }
+
+ svg {
+ width: 14px;
+ height: 14px;
+ }
+ }
+ }
+
+ .gradients-buttons {
+ .gradient {
+ cursor: pointer;
+ width: 15px;
+ height: 15px;
+ padding: 0;
+ margin: 0;
+ border: 1px solid $color-gray-20;
+ border-radius: 2px;
+ margin-left: 0.25rem;
}
- .top-actions {
- display: flex;
- margin-bottom: 0.25rem;
-
- .picker-btn {
- background: none;
- border: none;
- cursor: pointer;
-
- &.active,
- &:hover svg {
- fill: $color-primary;
- }
-
- svg {
- width: 14px;
- height: 14px;
- }
- }
+ .active {
+ border-color: $color-primary;
}
- .picker-detail-wrapper {
- position: relative;
+ .linear-gradient {
+ background: linear-gradient(180deg, $color-gray-20, transparent);
+ }
- .center-circle {
- width: 14px;
- height: 14px;
- border: 2px solid $color-white;
- border-radius: 8px;
- position: absolute;
- left: 50%;
- top: 50%;
- transform: translate(-7px, -7px);
- filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
- }
+ .radial-gradient {
+ background: radial-gradient(transparent, $color-gray-20);
}
- #picker-detail {
- border: 1px solid $color-gray-10;
+ }
+
+ .gradient-stops {
+ height: 10px;
+ display: flex;
+ margin-top: 0.5rem;
+ margin-bottom: 1rem;
+
+ .gradient-background {
+ height: 100%;
+ width: 100%;
+ border: 1px solid $color-gray-10;
}
+ .gradient-stop-wrapper {
+ position: absolute;
+ width: calc(100% - 2rem);
+ margin-left: 0.5rem;
+ }
+
+ .gradient-stop {
+ position: absolute;
+ width: 14px;
+ height: 14px;
+ border-radius: 2px;
+ border: 1px solid $color-gray-20;
+ margin-top: -2px;
+ margin-left: -7px;
+ box-shadow: 0 2px 2px rgb(0 0 0 / 15%);
+
+ .selected {
+ border-color: $color-primary;
+ }
+ }
+ }
+
+ .picker-detail-wrapper {
+ position: relative;
+
+ .center-circle {
+ width: 14px;
+ height: 14px;
+ border: 2px solid $color-white;
+ border-radius: 8px;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-7px, -7px);
+ filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
+ }
+ }
+
+ #picker-detail {
+ border: 1px solid $color-gray-10;
+ }
+
+ .slider-selector {
+ --gradient-direction: 90deg;
+ --background-repeat: left;
+
+ &.vertical {
+ --gradient-direction: 0deg;
+ --background-repeat: top;
+ }
+
+ border: 1px solid $color-gray-10;
+
+ background: linear-gradient(var(--gradient-direction), rgba(var(--color), 0) 0%, rgba(var(--color), 1.0) 100%);
+ align-self: center;
+ position: relative;
+ cursor: pointer;
+
+ width: 100%;
+ height: calc(0.5rem + 1px);
+
+ &.vertical {
+ width: calc(0.5rem + 1px);
+ height: 100%;
+ }
+
+ &.hue {
+ background: linear-gradient(
+ var(--gradient-direction),
+ #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%,
+ #00f 67%, #f0f 83%, #f00 100%);
+ }
+
+ &.saturation {
+ background: linear-gradient(
+ var(--gradient-direction),
+ var(--saturation-grad-from) 0%,
+ var(--saturation-grad-to) 100%
+ )
+ }
+
+ &.opacity {
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") var(--background-repeat) center;
+
+ &::after {
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(var(--gradient-direction), rgba(var(--color), 0) 0%, rgba(var(--color), 1.0) 100%);
+ }
+
+ }
+
+ &.value {
+ background: linear-gradient(var(--gradient-direction), #FFF 0%, #000 100%);
+ }
+
+
.handler {
+ background-color: $color-white;;
+ box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px;
+ transform: translate(-6px, -2px);
+ left: 50%;
+ position: absolute;
+ width: 12px;
+ height: 12px;
+ border-radius: 6px;
+ z-index: 1;
+ }
+
+ &.vertical .handler {
+ transform: translate(-6px, 6px);
+ }
+ }
+
+ .value-saturation-selector {
+ background-color: rgba(var(--hue-rgb));
+ position: relative;
+ height: 6.75rem;
+ cursor: pointer;
+
+ .handler {
+ position: absolute;
+ width: 12px;
+ height: 12px;
+ border-radius: 6px;
+ z-index: 1;
+ border: 1px solid $color-white;
+ box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset, rgb(0 0 0 / 0.25) 0px 4px 4px inset, rgb(0 0 0 / 0.25) 0px 4px 4px;
+ transform: translate(-6px, -6px);
+ left: 50%;
+ top: 50%;
+ }
+
+ &::before {
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(to right, #fff, rgba(255,255,255,0));
+ }
+
+ &::after {
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(to top, #000, rgba(0,0,0,0));
+ }
+ }
+
+ .color-bullet {
+ grid-area: color;
+ width: 20px;
+ height: 20px;
+ background-color: rgba(var(--color));
+ border-radius: 12px;
+ border: 1px solid $color-gray-10;
+ }
+
+ .shade-selector {
+ display: grid;
+ justify-items: center;
+ align-items: center;
+ grid-template-areas: "color hue"
+ "color opacity";
+ grid-template-columns: 2.5rem 1fr;
+ height: 3.5rem;
+ grid-row-gap: 0.5rem;
+ cursor: pointer;
+ margin-bottom: 0.25rem;
+
+ .slider-selector.hue {
+ grid-area: "hue";
+ align-self: end;
+ }
+
+ .slider-selector.opacity {
+ grid-area: "opacity";
+ align-self: start;
+ }
+ }
+
+ .color-values {
+ display: grid;
+ grid-template-columns: 3.5rem repeat(4, 1fr);
+ grid-row-gap: 0.25rem;
+ justify-items: center;
+ grid-column-gap: 0.25rem;
+
+ input {
+ width: 100%;
+ margin: 0;
+ border: 1px solid $color-gray-10;
+ border-radius: 2px;
+ font-size: $fs11;
+ height: 1.5rem;
+ padding: 0 $x-small;
+ color: $color-gray-40;
+ }
+
+ label {
+ font-size: $fs11;
+ }
+ }
+
+ .libraries {
+ border-top: 1px solid $color-gray-10;
+ padding-top: 0.5rem;
+ margin-top: 0.25rem;
+ width: 200px;
+
+ select {
+ background-image: url(/images/icons/arrow-down.svg);
+ background-repeat: no-repeat;
+ background-position: 95% 48%;
+ background-size: 10px;
+ margin: 0;
+ margin-bottom: 0.5rem;
+ width: 100%;
+ padding: 2px 0.25rem;
+ font-size: 0.75rem;
+ color: $color-gray-40;
+ border-color: $color-gray-10;
+ border-radius: 2px;
+
+ option {
+ padding: 0;
+ }
+ }
+
+ .selected-colors {
+ display: grid;
+ grid-template-columns: repeat(8, 1fr);
+ justify-content: space-between;
+ margin-right: -8px;
+ overflow-x: hidden;
+ overflow-y: auto;
+ max-height: 5.5rem;
+ }
+
+
+ .selected-colors::after {
+ content: "";
+ flex: auto;
+ }
+
+ .selected-colors .color-bullet {
+ grid-area: auto;
+ margin-bottom: 0.25rem;
+ cursor: pointer;
+
+ &:hover {
+ border-color: $color-primary;
+ }
+
+ &.button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ &.button svg {
+ width: 12px;
+ height: 12px;
+ fill: $color-gray-30;
+ }
+
+ &.plus-button svg {
+ width: 8px;
+ height: 8px;
+ fill: $color-black;
+ }
+ }
+ }
+
+ .actions {
+ margin-top: 0.5rem;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+
+ .btn-primary {
+ height: 1.5rem;
+ padding: 0 2.5rem;
+ font-size: $fs12;
+ }
+ }
+
+ .harmony-selector {
+ display: flex;
+ flex-direction: row;
+ margin-bottom: 0.5rem;
+
+ .hue-wheel-wrapper {
+ position: relative;
+
+ .hue-wheel {
+ width: 152px;
+ height: 152px;
+ }
+
+ .handler {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
z-index: 1;
- }
-
- .value-selector {
- background-color: rgba(var(--hue));
- position: relative;
- height: 6.75rem;
- cursor: pointer;
-
- .handler {
- box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset;
- transform: translate(-6px, -6px);
- left: 50%;
- top: 50%;
- }
- }
-
- .value-selector::before {
- content: "";
- position: absolute;
- width: 100%;
- height: 100%;
- background: linear-gradient(to right, #fff, rgba(255,255,255,0));
- }
-
- .value-selector::after {
- content: "";
- position: absolute;
- width: 100%;
- height: 100%;
- background: linear-gradient(to top, #000, rgba(0,0,0,0));
- }
-
- .shade-selector {
- display: grid;
- justify-items: center;
- align-items: center;
- grid-template-areas: "color hue" "color opacity";
- grid-template-columns: 2.5rem 1fr;
- height: 3.5rem;
- grid-row-gap: 0.5rem;
- cursor: pointer;
- }
-
- .color-bullet {
- grid-area: color;
- width: 20px;
- height: 20px;
- background-color: rgba(var(--color));
- border-radius: 12px;
- border: 1px solid $color-gray-10;
- }
-
- .hue-selector {
- align-self: end;
- grid-area: hue;
- height: 0.5rem;
- width: 100%;
- background: linear-gradient(
- to right,
- #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%,
- #00f 67%, #f0f 83%, #f00 100%);
- position: relative;
- cursor: pointer;
- }
-
- .hue-selector .handler,
- .opacity-selector .handler {
- background-color: rgb(248, 248, 248);
- box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px;
- transform: translate(-6px, -2px);
+ border: 1px solid $color-white;
+ box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset, rgb(0 0 0 / 0.25) 0px 4px 4px inset, rgb(0 0 0 / 0.25) 0px 4px 4px;
+ transform: translate(-6px, -6px);
left: 50%;
+ top: 50%;
+ }
+
+ .handler.complement {
+ background-color: $color-white;
+ box-shadow: rgb(0 0 0 / 0.25) 0px 4px 4px;
+ }
}
- .opacity-selector {
- align-self: start;
- grid-area: opacity;
- height: 0.5rem;
- width: 100%;
- position: relative;
- background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") left center;
- }
-
- .opacity-selector::after {
- content: "";
- background: linear-gradient(to right, rgba(var(--color), 0) 0%, rgba(var(--color), 1.0) 100%);
- position: absolute;
- width: 100%;
+ .handlers-wrapper {
+ height: 152px;
+ display: flex;
+ flex-direction: row;
+ flex-grow: 1;
+ justify-content: space-around;
+ padding-top: 0.5rem;
+
+ & > * {
height: 100%;
+ }
+ }
+ }
+
+ .hsva-selector {
+ display: grid;
+ padding: 0.25rem;
+ grid-template-columns: 20px 1fr;
+ grid-template-rows: repeat(4, 2rem);
+ grid-row-gap: 0.5rem;
+ margin-bottom: 0.5rem;
+
+ .hue,
+ .saturation,
+ .value,
+ .opacity {
+ border-radius: 10px;
}
- .color-values {
- display: grid;
- grid-template-columns: 3.5rem repeat(4, 1fr);
- grid-row-gap: 0.25rem;
- justify-items: center;
- grid-column-gap: 0.25rem;
+ .hsva-selector-label {
+ grid-column: 1;
+ align-self: center;
+ }
+ }
+}
- input {
- width: 100%;
- margin: 0;
- border: 1px solid $color-gray-10;
- border-radius: 2px;
- font-size: $fs11;
- height: 1.5rem;
- padding: 0 $x-small;
- color: $color-gray-40;
- }
+.colorpicker-tooltip {
+ border-radius: $br-small;
+ display: flex;
+ flex-direction: column;
+ left: 1400px;
+ top: 100px;
+ position: absolute;
+ z-index: 11;
+ width: auto;
- label {
- font-size: $fs11;
- }
+ span {
+ color: $color-gray-20;
+ font-size: $fs12;
+ }
+
+ .inputs-area {
+
+ .input-text {
+ color: $color-gray-60;
+ font-size: $fs13;
+ margin: 5px;
+ padding: 5px;
+ width: 100%;
}
- .libraries {
- border-top: 1px solid $color-gray-10;
- padding-top: 0.5rem;
- margin-top: 0.25rem;
- width: 200px;
-
- select {
- background-image: url(/images/icons/arrow-down.svg);
- background-repeat: no-repeat;
- background-position: 95% 48%;
- background-size: 10px;
- margin: 0;
- margin-bottom: 0.5rem;
- width: 100%;
- padding: 2px 0.25rem;
- font-size: 0.75rem;
- color: $color-gray-40;
- border-color: $color-gray-10;
- border-radius: 2px;
+ }
- option {
- padding: 0;
- }
- }
+ .colorpicker-tabs {
+ display: flex;
+ margin-top: 0.25rem;
+ height: 2rem;
+ background-color: $color-gray-10;
- .selected-colors {
- display: grid;
- grid-template-columns: repeat(8, 1fr);
- justify-content: space-between;
- margin-right: -8px;
- overflow-x: hidden;
- overflow-y: auto;
- max-height: 5.5rem;
- }
-
-
- .selected-colors::after {
- content: "";
- flex: auto;
- }
-
- .selected-colors .color-bullet {
- grid-area: auto;
- margin-bottom: 0.25rem;
- cursor: pointer;
-
- &:hover {
- border-color: $color-primary;
- }
-
- &.button {
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- &.button svg {
- width: 12px;
- height: 12px;
- fill: $color-gray-30;
- }
-
- &.plus-button svg {
- width: 8px;
- height: 8px;
- fill: $color-black;
- }
- }
+ .active {
+ background-color: $color-white;
}
- .actions {
- margin-top: 0.5rem;
- display: flex;
- flex-direction: row;
- justify-content: center;
+ .colorpicker-tab {
+ cursor: pointer;
+ display: flex;
+ flex-grow: 1;
+ justify-content: center;
+ align-items: center;
- .btn-primary {
- height: 1.5rem;
- padding: 0 2.5rem;
- font-size: $fs12;
- }
+ svg {
+ width: 16px;
+ height: 16px;
+ fill: $color-gray-30;
+ }
}
+ }
}
.color-data {
@@ -265,8 +495,8 @@
position: relative;
.color-name {
- font-size: $fs13;
- margin: 5px 6px 0px 6px;
+ font-size: $fs13;
+ margin: 5px 6px 0px 6px;
}
.color-info {
@@ -310,30 +540,3 @@
}
}
-.colorpicker-tooltip {
- border-radius: $br-small;
- display: flex;
- flex-direction: column;
- left: 1400px;
- top: 100px;
- position: absolute;
- z-index: 11;
- width: auto;
-
- span {
- color: $color-gray-20;
- font-size: $fs12;
- }
-
- .inputs-area {
-
- .input-text {
- color: $color-gray-60;
- font-size: $fs13;
- margin: 5px;
- padding: 5px;
- width: 100%;
- }
-
- }
-}
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index 34c34f550..4e28fc2ca 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -122,6 +122,9 @@
(def uppercase (icon-xref :uppercase))
(def user (icon-xref :user))
(def tick (icon-xref :tick))
+(def picker-harmony (icon-xref :picker-harmony))
+(def picker-hsv (icon-xref :picker-hsv))
+(def picker-ramp (icon-xref :picker-ramp))
(def loader-pencil
(mf/html
diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs
index 85d58aeb3..a8901ba2e 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs
@@ -10,18 +10,20 @@
(ns app.main.ui.workspace.colorpicker
(:require
[rumext.alpha :as mf]
- [app.main.store :as st]
+ [okulary.core :as l]
[cuerdas.core :as str]
- [app.util.dom :as dom]
- [app.util.color :as uc]
- [app.main.ui.icons :as i]
+ [app.common.geom.point :as gpt]
[app.common.math :as math]
[app.common.uuid :refer [uuid]]
+ [app.util.dom :as dom]
+ [app.util.color :as uc]
+ [app.util.object :as obj]
+ [app.main.store :as st]
+ [app.main.refs :as refs]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.colors :as dwc]
[app.main.data.modal :as modal]
- [okulary.core :as l]
- [app.main.refs :as refs]
+ [app.main.ui.icons :as i]
[app.util.i18n :as i18n :refer [t]]))
;; --- Refs
@@ -44,7 +46,7 @@
;; --- Color Picker Modal
-(mf/defc value-selector [{:keys [hue saturation value on-change]}]
+(mf/defc value-saturation-selector [{:keys [hue saturation value on-change]}]
(let [dragging? (mf/use-state false)
calculate-pos
(fn [ev]
@@ -53,7 +55,7 @@
px (math/clamp (/ (- x left) (- right left)) 0 1)
py (* 255 (- 1 (math/clamp (/ (- y top) (- bottom top)) 0 1)))]
(on-change px py)))]
- [:div.value-selector
+ [:div.value-saturation-selector
{:on-mouse-down #(reset! dragging? true)
:on-mouse-up #(reset! dragging? false)
:on-pointer-down (partial dom/capture-pointer)
@@ -64,41 +66,389 @@
:left (str (* 100 saturation) "%")
:top (str (* 100 (- 1 (/ value 255))) "%")}}]]))
-(mf/defc hue-selector [{:keys [hue on-change]}]
- (let [dragging? (mf/use-state false)
- calculate-pos
- (fn [ev]
- (let [{:keys [left right]} (-> ev dom/get-target dom/get-bounding-rect)
- {:keys [x]} (-> ev dom/get-client-position)
- px (math/clamp (/ (- x left) (- right left)) 0 1)]
- (on-change (* px 360))))]
- [:div.hue-selector
- {:on-mouse-down #(reset! dragging? true)
- :on-mouse-up #(reset! dragging? false)
- :on-pointer-down (partial dom/capture-pointer)
- :on-pointer-up (partial dom/release-pointer)
- :on-click calculate-pos
- :on-mouse-move #(when @dragging? (calculate-pos %))}
- [:div.handler {:style {:pointer-events "none"
- :left (str (* (/ hue 360) 100) "%")}}]]))
-(mf/defc opacity-selector [{:keys [opacity on-change]}]
- (let [dragging? (mf/use-state false)
+(mf/defc slider-selector [{:keys [value class min-value max-value vertical? reverse? on-change]}]
+ (let [min-value (or min-value 0)
+ max-value (or max-value 1)
+ dragging? (mf/use-state false)
calculate-pos
(fn [ev]
- (let [{:keys [left right]} (-> ev dom/get-target dom/get-bounding-rect)
- {:keys [x]} (-> ev dom/get-client-position)
- px (math/clamp (/ (- x left) (- right left)) 0 1)]
- (on-change px)))]
- [:div.opacity-selector
- {:on-mouse-down #(reset! dragging? true)
+ (when on-change
+ (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
+ {:keys [x y]} (-> ev dom/get-client-position)
+ unit-value (if vertical?
+ (math/clamp (/ (- bottom y) (- bottom top)) 0 1)
+ (math/clamp (/ (- x left) (- right left)) 0 1))
+ unit-value (if reverse?
+ (math/abs (- unit-value 1.0))
+ unit-value)
+ value (+ min-value (* unit-value (- max-value min-value)))]
+ (on-change value))))]
+
+ [:div.slider-selector
+ {:class (str (if vertical? "vertical " "") class)
+ :on-mouse-down #(reset! dragging? true)
:on-mouse-up #(reset! dragging? false)
:on-pointer-down (partial dom/capture-pointer)
:on-pointer-up (partial dom/release-pointer)
:on-click calculate-pos
:on-mouse-move #(when @dragging? (calculate-pos %))}
- [:div.handler {:style {:pointer-events "none"
- :left (str (* opacity 100) "%")}}]]))
+
+ (let [value-percent (* (/ (- value min-value)
+ (- max-value min-value)) 100)
+
+ value-percent (if reverse?
+ (math/abs (- value-percent 100))
+ value-percent)
+ value-percent-str (str value-percent "%")
+
+ style-common #js {:pointerEvents "none"}
+ style-horizontal (obj/merge! #js {:left value-percent-str} style-common)
+ style-vertical (obj/merge! #js {:bottom value-percent-str} style-common)]
+ [:div.handler {:style (if vertical? style-vertical style-horizontal)}])]))
+
+
+(defn create-color-wheel
+ [canvas-node]
+ (let [ctx (.getContext canvas-node "2d")
+ width (obj/get canvas-node "width")
+ height (obj/get canvas-node "height")
+ radius (/ width 2)
+ cx (/ width 2)
+ cy (/ width 2)
+ step 0.2]
+
+ (.clearRect ctx 0 0 width height)
+
+ (doseq [degrees (range 0 360 step)]
+ (let [degrees-rad (math/radians degrees)
+ x (* radius (math/cos (- degrees-rad)))
+ y (* radius (math/sin (- degrees-rad)))]
+ (obj/set! ctx "strokeStyle" (str/format "hsl(%s, 100%, 50%)" degrees))
+ (.beginPath ctx)
+ (.moveTo ctx cx cy)
+ (.lineTo ctx (+ cx x) (+ cy y))
+ (.stroke ctx)))
+
+ (let [grd (.createRadialGradient ctx cx cy 0 cx cx radius)]
+ (.addColorStop grd 0 "white")
+ (.addColorStop grd 1 "rgba(255, 255, 255, 0")
+ (obj/set! ctx "fillStyle" grd)
+
+ (.beginPath ctx)
+ (.arc ctx cx cy radius 0 (* 2 math/PI) true)
+ (.closePath ctx)
+ (.fill ctx))))
+
+(mf/defc ramp-selector [{:keys [color on-change]}]
+ (let [{hue :h saturation :s value :v alpha :alpha} color
+
+ on-change-value-saturation
+ (fn [new-saturation new-value]
+ (let [hex (uc/hsv->hex [hue new-saturation new-value])
+ [r g b] (uc/hex->rgb hex)]
+ (on-change {:hex hex
+ :r r :g g :b b
+ :s new-saturation
+ :v new-value})))
+
+ on-change-hue
+ (fn [new-hue]
+ (let [hex (uc/hsv->hex [new-hue saturation value])
+ [r g b] (uc/hex->rgb hex)]
+ (on-change {:hex hex
+ :r r :g g :b b
+ :h new-hue} )))
+
+ on-change-opacity
+ (fn [new-opacity]
+ (on-change {:alpha new-opacity} ))]
+ [:*
+ [:& value-saturation-selector
+ {:hue hue
+ :saturation saturation
+ :value value
+ :on-change on-change-value-saturation}]
+
+ [:div.shade-selector
+ [:div.color-bullet]
+ [:& slider-selector {:class "hue"
+ :max-value 360
+ :value hue
+ :on-change on-change-hue}]
+
+ [:& slider-selector {:class "opacity"
+ :max-value 1
+ :value alpha
+ :on-change on-change-opacity}]]]))
+
+(defn color->point
+ [canvas-side hue saturation]
+ (let [hue-rad (math/radians (- hue))
+ comp-x (* saturation (math/cos hue-rad))
+ comp-y (* saturation (math/sin hue-rad))
+ x (+ (/ canvas-side 2) (* comp-x (/ canvas-side 2)))
+ y (+ (/ canvas-side 2) (* comp-y (/ canvas-side 2)))]
+ (gpt/point x y)))
+
+(mf/defc harmony-selector [{:keys [color on-change]}]
+ (let [canvas-ref (mf/use-ref nil)
+ {hue :h saturation :s value :v alpha :alpha} color
+
+ canvas-side 152
+ pos-current (color->point canvas-side hue saturation)
+ pos-complement (color->point canvas-side (mod (+ hue 180) 360) saturation)
+ dragging? (mf/use-state false)
+
+ calculate-pos (fn [ev]
+ (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
+ {:keys [x y]} (-> ev dom/get-client-position)
+ px (math/clamp (/ (- x left) (- right left)) 0 1)
+ py (math/clamp (/ (- y top) (- bottom top)) 0 1)
+
+ px (- (* 2 px) 1)
+ py (- (* 2 py) 1)
+
+ angle (math/degrees (math/atan2 px py))
+ new-hue (math/precision (mod (- angle 90 ) 360) 2)
+ new-saturation (math/clamp (math/distance [px py] [0 0]) 0 1)
+ hex (uc/hsv->hex [new-hue new-saturation value])
+ [r g b] (uc/hex->rgb hex)]
+ (on-change {:hex hex
+ :r r :g g :b b
+ :h new-hue
+ :s new-saturation})))
+
+ on-change-value (fn [new-value]
+ (let [hex (uc/hsv->hex [hue saturation new-value])
+ [r g b] (uc/hex->rgb hex)]
+ (on-change {:hex hex
+ :r r :g g :b b
+ :v new-value})))
+ on-complement-click (fn [ev]
+ (let [new-hue (mod (+ hue 180) 360)
+ hex (uc/hsv->hex [new-hue saturation value])
+ [r g b] (uc/hex->rgb hex)]
+ (on-change {:hex hex
+ :r r :g g :b b
+ :h new-hue
+ :s saturation})))
+
+ on-change-opacity (fn [new-alpha] (on-change {:alpha new-alpha}))]
+
+ (mf/use-effect
+ (mf/deps canvas-ref)
+ (fn [] (when canvas-ref
+ (create-color-wheel (mf/ref-val canvas-ref)))))
+
+ [:div.harmony-selector
+ [:div.hue-wheel-wrapper
+ [:canvas.hue-wheel
+ {:ref canvas-ref
+ :width canvas-side
+ :height canvas-side
+ :on-mouse-down #(reset! dragging? true)
+ :on-mouse-up #(reset! dragging? false)
+ :on-pointer-down (partial dom/capture-pointer)
+ :on-pointer-up (partial dom/release-pointer)
+ :on-click calculate-pos
+ :on-mouse-move #(when @dragging? (calculate-pos %))}]
+ [:div.handler {:style {:pointer-events "none"
+ :left (:x pos-current)
+ :top (:y pos-current)}}]
+ [:div.handler.complement {:style {:left (:x pos-complement)
+ :top (:y pos-complement)
+ :cursor "pointer"}
+ :on-click on-complement-click}]]
+ [:div.handlers-wrapper
+ [:& slider-selector {:class "value"
+ :vertical? true
+ :reverse? true
+ :value value
+ :max-value 255
+ :vertical true
+ :on-change on-change-value}]
+ [:& slider-selector {:class "opacity"
+ :vertical? true
+ :value alpha
+ :max-value 1
+ :vertical true
+ :on-change on-change-opacity}]]]))
+
+(mf/defc hsva-selector [{:keys [color on-change]}]
+ (let [{hue :h saturation :s value :v alpha :alpha} color
+ handle-change-slider (fn [key]
+ (fn [new-value]
+ (let [change (hash-map key new-value)
+ {:keys [h s v]} (merge color change)
+ hex (uc/hsv->hex [h s v])
+ [r g b] (uc/hex->rgb hex)]
+ (on-change (merge change
+ {:hex hex
+ :r r :g g :b b})))))
+ on-change-opacity (fn [new-alpha] (on-change {:alpha new-alpha}))]
+ [:div.hsva-selector
+ [:span.hsva-selector-label "H"]
+ [:& slider-selector
+ {:class "hue" :max-value 360 :value hue :on-change (handle-change-slider :h)}]
+
+ [:span.hsva-selector-label "S"]
+ [:& slider-selector
+ {:class "saturation" :max-value 1 :value saturation :on-change (handle-change-slider :s)}]
+
+ [:span.hsva-selector-label "V"]
+ [:& slider-selector
+ {:class "value" :reverse? true :max-value 255 :value value :on-change (handle-change-slider :v)}]
+
+ [:span.hsva-selector-label "A"]
+ [:& slider-selector
+ {:class "opacity" :max-value 1 :value alpha :on-change on-change-opacity}]]))
+
+(mf/defc color-inputs [{:keys [type color on-change]}]
+ (let [{red :r green :g blue :b
+ hue :h saturation :s value :v
+ hex :hex alpha :alpha} color
+
+ parse-hex (fn [val] (if (= (first val) \#) val (str \# val)))
+
+ refs {:hex (mf/use-ref nil)
+ :r (mf/use-ref nil)
+ :g (mf/use-ref nil)
+ :b (mf/use-ref nil)
+ :h (mf/use-ref nil)
+ :s (mf/use-ref nil)
+ :v (mf/use-ref nil)
+ :alpha (mf/use-ref nil)}
+
+ on-change-hex
+ (fn [e]
+ (let [val (-> e dom/get-target-val parse-hex)]
+ (when (uc/hex? val)
+ (let [[r g b] (uc/hex->rgb val)
+ [h s v] (uc/hex->hsv hex)]
+ (on-change {:hex val
+ :h h :s s :v v
+ :r r :g g :b b})))))
+
+ on-change-property
+ (fn [property max-value]
+ (fn [e]
+ (let [val (-> e dom/get-target-val (math/clamp 0 max-value))
+ val (if (#{:s} property) (/ val 100) val)]
+ (when (not (nil? val))
+ (if (#{:r :g :b} property)
+ (let [{:keys [r g b]} (merge color (hash-map property val))
+ hex (uc/rgb->hex [r g b])
+ [h s v] (uc/hex->hsv hex)]
+ (on-change {:hex hex
+ :h h :s s :v v
+ :r r :g g :b b}))
+
+ (let [{:keys [h s v]} (merge color (hash-map property val))
+ hex (uc/hsv->hex [h s v])
+ [r g b] (uc/hex->rgb hex)]
+ (on-change {:hex hex
+ :h h :s s :v v
+ :r r :g g :b b})))))))
+
+ on-change-opacity
+ (fn [e]
+ (when-let [new-alpha (-> e dom/get-target-val (math/clamp 0 100) (/ 100))]
+ (on-change {:alpha new-alpha})))]
+
+
+ ;; Updates the inputs values when a property is changed in the parent
+ (mf/use-effect
+ (mf/deps color type)
+ (fn []
+ (doseq [ref-key (keys refs)]
+ (let [property-val (get color ref-key)
+ property-ref (get refs ref-key)]
+ (when (and property-val property-ref)
+ (when-let [node (mf/ref-val property-ref)]
+ (case ref-key
+ (:s :alpha) (dom/set-value! node (math/round (* property-val 100)))
+ :hex (dom/set-value! node property-val)
+ (dom/set-value! node (math/round property-val)))))))))
+
+ [:div.color-values
+ [:input {:id "hex-value"
+ :ref (:hex refs)
+ :default-value hex
+ :on-change on-change-hex}]
+
+ (if (= type :rgb)
+ [:*
+ [:input {:id "red-value"
+ :ref (:r refs)
+ :type "number"
+ :min 0
+ :max 255
+ :default-value red
+ :on-change (on-change-property :r 255)}]
+
+ [:input {:id "green-value"
+ :ref (:g refs)
+ :type "number"
+ :min 0
+ :max 255
+ :default-value green
+ :on-change (on-change-property :g 255)}]
+
+ [:input {:id "blue-value"
+ :ref (:b refs)
+ :type "number"
+ :min 0
+ :max 255
+ :default-value blue
+ :on-change (on-change-property :b 255)}]]
+ [:*
+ [:input {:id "hue-value"
+ :ref (:h refs)
+ :type "number"
+ :min 0
+ :max 360
+ :default-value hue
+ :on-change (on-change-property :h 360)}]
+
+ [:input {:id "saturation-value"
+ :ref (:s refs)
+ :type "number"
+ :min 0
+ :max 100
+ :step 1
+ :default-value saturation
+ :on-change (on-change-property :s 100)}]
+
+ [:input {:id "value-value"
+ :ref (:v refs)
+ :type "number"
+ :min 0
+ :max 255
+ :default-value value
+ :on-change (on-change-property :v 255)}]])
+
+ [:input.alpha-value {:id "alpha-value"
+ :ref (:alpha refs)
+ :type "number"
+ :min 0
+ :step 1
+ :max 100
+ :default-value (if (= alpha :multiple) "" (math/precision alpha 2))
+ :on-change on-change-opacity}]
+
+ [:label.hex-label {:for "hex-value"} "HEX"]
+ (if (= type :rgb)
+ [:*
+ [:label.red-label {:for "red-value"} "R"]
+ [:label.green-label {:for "green-value"} "G"]
+ [:label.blue-label {:for "blue-value"} "B"]]
+ [:*
+ [:label.red-label {:for "hue-value"} "H"]
+ [:label.green-label {:for "saturation-value"} "S"]
+ [:label.blue-label {:for "value-value"} "V"]])
+ [:label.alpha-label {:for "alpha-value"} "A"]]))
+
(defn as-color-components [value opacity]
(let [value (if (uc/hex? value) value "#000000")
@@ -108,12 +458,13 @@
{:hex (or value "000000")
:alpha (or opacity 1)
:r r :g g :b b
- :h h :s s :v v}
- ))
+ :h h :s s :v v}))
(mf/defc colorpicker
[{:keys [value opacity on-change on-accept]}]
(let [current-color (mf/use-state (as-color-components value opacity))
+
+ active-tab (mf/use-state :ramp #_:harmony #_:hsva)
selected-library (mf/use-state "recent")
current-library-colors (mf/use-state [])
ref-picker (mf/use-ref)
@@ -136,7 +487,16 @@
parse-selected (fn [selected]
(if (#{"recent" "file"} selected)
(keyword selected)
- (uuid selected)) )]
+ (uuid selected)) )
+
+ change-tab (fn [tab] #(reset! active-tab tab))
+
+ handle-change-color (fn [changes]
+ (swap! current-color merge changes)
+ (when (:hex changes)
+ (reset! value-ref (:hex changes)))
+ (on-change (:hex changes (:hex @current-color))
+ (:alpha changes (:alpha @current-color))))]
;; Update state when there is a change in the props upstream
(mf/use-effect
@@ -149,9 +509,19 @@
(mf/deps @current-color)
(fn [] (let [node (mf/ref-val ref-picker)
rgb [(:r @current-color) (:g @current-color) (:b @current-color)]
- hue-rgb (uc/hsv->rgb [(:h @current-color) 1.0 255])]
+ hue-rgb (uc/hsv->rgb [(:h @current-color) 1.0 255])
+ hsl-from (uc/hsv->hsl [(:h @current-color) 0 (:v @current-color)])
+ hsl-to (uc/hsv->hsl [(:h @current-color) 1 (:v @current-color)])
+
+ format-hsl (fn [[h s l]]
+ (str/fmt "hsl(%s, %s, %s)"
+ h
+ (str (* s 100) "%")
+ (str (* l 100) "%")))]
(dom/set-css-property node "--color" (str/join ", " rgb))
- (dom/set-css-property node "--hue" (str/join ", " hue-rgb)))))
+ (dom/set-css-property node "--hue-rgb" (str/join ", " hue-rgb))
+ (dom/set-css-property node "--saturation-grad-from" (format-hsl hsl-from))
+ (dom/set-css-property node "--saturation-grad-to" (format-hsl hsl-to)))))
;; Load library colors when the select is changed
(mf/use-effect
@@ -204,168 +574,78 @@
(on-change (:hex @current-color) (:alpha @current-color) nil nil picked-shift?))))
[:div.colorpicker {:ref ref-picker}
- [:div.top-actions
- [:button.picker-btn
- {:class (when picking-color? "active")
- :on-click (fn []
- (modal/allow-click-outside!)
- (st/emit! (dwc/start-picker)))}
- i/picker]]
+ [:div.colorpicker-content
+ [:div.top-actions
+ [:button.picker-btn
+ {:class (when picking-color? "active")
+ :on-click (fn []
+ (modal/allow-click-outside!)
+ (st/emit! (dwc/start-picker)))}
+ i/picker]
- (if picking-color?
- [:div.picker-detail-wrapper
- [:div.center-circle]
- [:canvas#picker-detail {:width 200
- :height 160}]]
- [:& value-selector {:hue (:h @current-color)
- :saturation (:s @current-color)
- :value (:v @current-color)
- :on-change (fn [s v]
- (let [hex (uc/hsv->hex [(:h @current-color) s v])
- [r g b] (uc/hex->rgb hex)]
- (swap! current-color assoc
- :hex hex
- :r r :g g :b b
- :s s :v v)
- (reset! value-ref hex)
- (on-change hex (:alpha @current-color))))}])
- (when (not picking-color?)
- [:div.shade-selector
- [:div.color-bullet]
- [:& hue-selector {:hue (:h @current-color)
- :on-change (fn [h]
- (let [hex (uc/hsv->hex [h (:s @current-color) (:v @current-color)])
- [r g b] (uc/hex->rgb hex)]
- (swap! current-color assoc
- :hex hex
- :r r :g g :b b
- :h h )
- (reset! value-ref hex)
- (on-change hex (:alpha @current-color))))}]
- [:& opacity-selector {:opacity (:alpha @current-color)
- :on-change (fn [alpha]
- (swap! current-color assoc :alpha alpha)
- (on-change (:hex @current-color) alpha))}]])
+ [:div.gradients-buttons
+ [:button.gradient.linear-gradient #_{:class "active"}]
+ [:button.gradient.radial-gradient]]]
- [:div.color-values
- [:input.hex-value {:id "hex-value"
- :value (:hex @current-color)
- :on-change (fn [e]
- (let [val (-> e dom/get-target dom/get-value)
- val (if (= (first val) \#) val (str \# val))]
- (swap! current-color assoc :hex val)
- (when (uc/hex? val)
- (reset! value-ref val)
- (let [[r g b] (uc/hex->rgb val)
- [h s v] (uc/hex->hsv val)]
+ #_[:div.gradient-stops
+ [:div.gradient-background {:style {:background "linear-gradient(90deg, #EC0BE5, #CDCDCD)" }}]
+ [:div.gradient-stop-wrapper
+ [:div.gradient-stop.start {:style {:background-color "#EC0BE5"}}]
+ [:div.gradient-stop.end {:style {:background-color "#CDCDCD"
+ :left "100%"}}]]]
+
+ (if picking-color?
+ [:div.picker-detail-wrapper
+ [:div.center-circle]
+ [:canvas#picker-detail {:width 200 :height 160}]]
+ (case @active-tab
+ :ramp [:& ramp-selector {:color @current-color :on-change handle-change-color}]
+ :harmony [:& harmony-selector {:color @current-color :on-change handle-change-color}]
+ :hsva [:& hsva-selector {:color @current-color :on-change handle-change-color}]
+ nil))
+
+ [:& color-inputs {:type (if (= @active-tab :hsva) :hsv :rgb) :color @current-color :on-change handle-change-color}]
+
+ [:div.libraries
+ [:select {:on-change (fn [e]
+ (let [val (-> e dom/get-target dom/get-value)]
+ (reset! selected-library val)))
+ :value @selected-library}
+ [:option {:value "recent"} (t locale "workspace.libraries.colors.recent-colors")]
+ [:option {:value "file"} (t locale "workspace.libraries.colors.file-library")]
+ (for [[_ {:keys [name id]}] shared-libs]
+ [:option {:key id
+ :value id} name])]
+
+ [:div.selected-colors
+ (when (= "file" @selected-library)
+ [:div.color-bullet.button.plus-button {:style {:background-color "white"}
+ :on-click #(st/emit! (dwl/add-color (:hex @current-color)))}
+ i/plus])
+
+ [:div.color-bullet.button {:style {:background-color "white"}
+ :on-click #(st/emit! (dwc/show-palette (parse-selected @selected-library)))}
+ i/palette]
+
+ (for [[idx {:keys [id file-id value]}] (map-indexed vector @current-library-colors)]
+ [:div.color-bullet {:key (str "color-" idx)
+ :on-click (fn []
+ (swap! current-color assoc :hex value)
+ (reset! value-ref value)
+ (let [[r g b] (uc/hex->rgb value)
+ [h s v] (uc/hex->hsv value)]
(swap! current-color assoc
:r r :g g :b b
:h h :s s :v v)
- (on-change val (:alpha @current-color))))))}]
- [:input.red-value {:id "red-value"
- :type "number"
- :min 0
- :max 255
- :value (:r @current-color)
- :on-change (fn [e]
- (let [val (-> e dom/get-target dom/get-value (math/clamp 0 255))]
- (swap! current-color assoc :r val)
- (when (not (nil? val))
- (let [{:keys [g b]} @current-color
- hex (uc/rgb->hex [val g b])
- [h s v] (uc/hex->hsv hex)]
- (reset! value-ref hex)
- (swap! current-color assoc
- :hex hex
- :h h :s s :v v)
- (on-change hex (:alpha @current-color))))))}]
- [:input.green-value {:id "green-value"
- :type "number"
- :min 0
- :max 255
- :value (:g @current-color)
- :on-change (fn [e]
- (let [val (-> e dom/get-target dom/get-value (math/clamp 0 255))]
- (swap! current-color assoc :g val)
- (when (not (nil? val))
- (let [{:keys [r b]} @current-color
- hex (uc/rgb->hex [r val b])
- [h s v] (uc/hex->hsv hex)]
- (reset! value-ref hex)
- (swap! current-color assoc
- :hex hex
- :h h :s s :v v)
- (on-change hex (:alpha @current-color))))))}]
- [:input.blue-value {:id "blue-value"
- :type "number"
- :min 0
- :max 255
- :value (:b @current-color)
- :on-change (fn [e]
- (let [val (-> e dom/get-target dom/get-value (math/clamp 0 255))]
- (swap! current-color assoc :b val)
- (when (not (nil? val))
- (let [{:keys [r g]} @current-color
- hex (uc/rgb->hex [r g val])
- [h s v] (uc/hex->hsv hex)]
- (reset! value-ref hex)
- (swap! current-color assoc
- :hex hex
- :h h :s s :v v)
- (on-change hex (:alpha @current-color))))))}]
- [:input.alpha-value {:id "alpha-value"
- :type "number"
- :min 0
- :step 0.1
- :max 1
- :value (if (= (:alpha @current-color) :multiple)
- ""
- (math/precision (:alpha @current-color) 2))
- :on-change (fn [e]
- (let [val (-> e dom/get-target dom/get-value (math/clamp 0 1))]
- (swap! current-color assoc :alpha val)
- (on-change (:hex @current-color) val)))}]
- [:label.hex-label {:for "hex-value"} "HEX"]
- [:label.red-label {:for "red-value"} "R"]
- [:label.green-label {:for "green-value"} "G"]
- [:label.blue-label {:for "blue-value"} "B"]
- [:label.alpha-label {:for "alpha-value"} "A"]]
-
- [:div.libraries
- [:select {:on-change (fn [e]
- (let [val (-> e dom/get-target dom/get-value)]
- (reset! selected-library val)))
- :value @selected-library}
- [:option {:value "recent"} (t locale "workspace.libraries.colors.recent-colors")]
- [:option {:value "file"} (t locale "workspace.libraries.colors.file-library")]
- (for [[_ {:keys [name id]}] shared-libs]
- [:option {:key id
- :value id} name])]
-
- [:div.selected-colors
- (when (= "file" @selected-library)
- [:div.color-bullet.button.plus-button {:style {:background-color "white"}
- :on-click #(st/emit! (dwl/add-color (:hex @current-color)))}
- i/plus])
-
- [:div.color-bullet.button {:style {:background-color "white"}
- :on-click #(st/emit! (dwc/show-palette (parse-selected @selected-library)))}
- i/palette]
-
- (for [[idx {:keys [id file-id value]}] (map-indexed vector @current-library-colors)]
- [:div.color-bullet {:key (str "color-" idx)
- :on-click (fn []
- (swap! current-color assoc :hex value)
- (reset! value-ref value)
- (let [[r g b] (uc/hex->rgb value)
- [h s v] (uc/hex->hsv value)]
- (swap! current-color assoc
- :r r :g g :b b
- :h h :s s :v v)
- (on-change value (:alpha @current-color) id file-id)))
- :style {:background-color value}}])]
-
- ]
+ (on-change value (:alpha @current-color) id file-id)))
+ :style {:background-color value}}])]]]
+ [:div.colorpicker-tabs
+ [:div.colorpicker-tab {:class (when (= @active-tab :ramp) "active")
+ :on-click (change-tab :ramp)} i/picker-ramp]
+ [:div.colorpicker-tab {:class (when (= @active-tab :harmony) "active")
+ :on-click (change-tab :harmony)} i/picker-harmony]
+ [:div.colorpicker-tab {:class (when (= @active-tab :hsva) "active")
+ :on-click (change-tab :hsva)} i/picker-hsv]]
(when on-accept
[:div.actions
[:button.btn-primary.btn-large
diff --git a/frontend/src/app/main/ui/workspace/gradients.cljs b/frontend/src/app/main/ui/workspace/gradients.cljs
index dad8b2099..0fc1e92a8 100644
--- a/frontend/src/app/main/ui/workspace/gradients.cljs
+++ b/frontend/src/app/main/ui/workspace/gradients.cljs
@@ -91,6 +91,8 @@
(def checkboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAACvUlEQVQoFQGyAk39AeLi4gAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB////AAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjScaa0cU7nIAAAAASUVORK5CYII=")
+#_(def checkboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=")
+
(mf/defc gradient-color-handler
[{:keys [filter-id zoom point color angle on-click on-mouse-down on-mouse-up]}]
[:g {:filter (str/fmt "url(#%s)" filter-id)
diff --git a/frontend/src/app/main/ui/workspace/selection.cljs b/frontend/src/app/main/ui/workspace/selection.cljs
index 41480a39a..b5e281510 100644
--- a/frontend/src/app/main/ui/workspace/selection.cljs
+++ b/frontend/src/app/main/ui/workspace/selection.cljs
@@ -212,7 +212,7 @@
:resize-point [:> resize-point-handler props]
:resize-side [:> resize-side-handler props])))
- (when (= :rect (:type shape))
+ #_(when (= :rect (:type shape))
[:& gradient-handlers {:shape tr-shape
:zoom zoom}])])))