From 14496f5c45f1116eb772b0bc046728f90e9906cb Mon Sep 17 00:00:00 2001 From: Baku Hashimoto Date: Mon, 2 Oct 2023 10:53:45 +0900 Subject: [PATCH] Update Tweeq library --- src/components/App.vue | 15 +- src/tweeq/FloatingPane/FloatingPane.vue | 2 - src/tweeq/GlslCanvas.vue | 151 +++++---- src/tweeq/InputButton.vue | 7 +- src/tweeq/InputCheckbox/InputCheckbox.vue | 3 +- src/tweeq/InputColor/InputColor.vue | 293 ++++++++++++++++++ src/tweeq/InputColor/InputColorChannelPad.vue | 105 +++++++ .../InputColor/InputColorChannelSlider.vue | 97 ++++++ .../InputColor/InputColorChannelValues.vue | 192 ++++++++++++ src/tweeq/InputColor/InputColorPicker.vue | 150 +++++++++ src/tweeq/InputColor/InputColorPresets.vue | 46 +++ src/tweeq/InputColor/common.styl | 20 ++ src/tweeq/InputColor/index.ts | 4 + src/tweeq/InputColor/pad.frag | 83 +++++ src/tweeq/InputColor/slider.frag | 62 ++++ src/tweeq/InputColor/types.ts | 150 +++++++++ src/tweeq/InputColor/useInputColor.ts | 18 ++ .../InputColorPicker/InputColorPicker.vue | 149 --------- src/tweeq/InputColorPicker/SliderAlpha.vue | 114 ------- src/tweeq/InputColorPicker/SliderHSV.vue | 218 ------------- .../InputColorPicker/SliderHSVRadial.vue | 198 ------------ src/tweeq/InputColorPicker/SliderRGB.vue | 97 ------ src/tweeq/InputColorPicker/common.styl | 17 - src/tweeq/InputColorPicker/index.ts | 2 - .../InputColorPicker/picker-hsv-pad.frag | 50 --- .../InputColorPicker/picker-hsv-radial.frag | 28 -- .../InputColorPicker/picker-hsv-slider.frag | 32 -- src/tweeq/InputColorPicker/use-hsv.ts | 66 ---- .../InputCubicBezier/InputCubicBezier.vue | 121 ++++---- .../InputCubicBezierPicker.vue | 155 +++++---- src/tweeq/InputCubicBezier/index.ts | 3 +- src/tweeq/InputCubicBezier/types.ts | 1 - src/tweeq/InputCubicBezier/util.ts | 1 + src/tweeq/InputDropdown.vue | 66 ++-- src/tweeq/InputNumber/InputNumber.vue | 72 +++-- src/tweeq/InputRadio.vue | 84 ++--- src/tweeq/InputRotery/InputRotery.vue | 52 ++-- src/tweeq/InputSeed/InputSeed.vue | 14 +- src/tweeq/InputShaderSlider.vue | 122 -------- src/tweeq/InputString/InputString.vue | 54 +++- src/tweeq/ParameterGrid/ParameterGrid.vue | 2 +- src/tweeq/Popover.vue | 29 +- src/tweeq/common.styl | 31 +- src/tweeq/glsl.d.ts | 4 + src/tweeq/index.ts | 6 +- src/tweeq/types.ts | 37 +++ src/tweeq/useDragV1.ts | 11 +- src/tweeq/useHSV.ts | 6 +- src/tweeq/useTheme.ts | 16 + src/tweeq/useTweeq.ts | 3 + src/tweeq/util.ts | 21 -- 51 files changed, 1790 insertions(+), 1490 deletions(-) create mode 100644 src/tweeq/InputColor/InputColor.vue create mode 100644 src/tweeq/InputColor/InputColorChannelPad.vue create mode 100644 src/tweeq/InputColor/InputColorChannelSlider.vue create mode 100644 src/tweeq/InputColor/InputColorChannelValues.vue create mode 100644 src/tweeq/InputColor/InputColorPicker.vue create mode 100644 src/tweeq/InputColor/InputColorPresets.vue create mode 100644 src/tweeq/InputColor/common.styl create mode 100644 src/tweeq/InputColor/index.ts create mode 100644 src/tweeq/InputColor/pad.frag create mode 100644 src/tweeq/InputColor/slider.frag create mode 100644 src/tweeq/InputColor/types.ts create mode 100644 src/tweeq/InputColor/useInputColor.ts delete mode 100644 src/tweeq/InputColorPicker/InputColorPicker.vue delete mode 100644 src/tweeq/InputColorPicker/SliderAlpha.vue delete mode 100644 src/tweeq/InputColorPicker/SliderHSV.vue delete mode 100644 src/tweeq/InputColorPicker/SliderHSVRadial.vue delete mode 100644 src/tweeq/InputColorPicker/SliderRGB.vue delete mode 100644 src/tweeq/InputColorPicker/common.styl delete mode 100644 src/tweeq/InputColorPicker/index.ts delete mode 100644 src/tweeq/InputColorPicker/picker-hsv-pad.frag delete mode 100644 src/tweeq/InputColorPicker/picker-hsv-radial.frag delete mode 100644 src/tweeq/InputColorPicker/picker-hsv-slider.frag delete mode 100644 src/tweeq/InputColorPicker/use-hsv.ts delete mode 100644 src/tweeq/InputCubicBezier/types.ts create mode 100644 src/tweeq/InputCubicBezier/util.ts delete mode 100644 src/tweeq/InputShaderSlider.vue create mode 100644 src/tweeq/glsl.d.ts create mode 100644 src/tweeq/types.ts diff --git a/src/components/App.vue b/src/components/App.vue index 465eb2f..32eebc6 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -38,6 +38,8 @@ const testNumber = ref(Math.PI) const testBoolean = ref(false) const testAlign = ref<'left' | 'center' | 'right'>('left') const colorSpace = ref<'r|g|b' | 'svh' | 'hsv' | 'hvs' | 'hsvr'>('hsv') +const cubicBezier = ref([0, 0.5, 0.5, 1] as const) +const testColor = ref('skyblue') // interface PaperDesc { // id?: string @@ -401,6 +403,7 @@ window.addEventListener('drop', async e => { + { - + @@ -425,19 +428,25 @@ window.addEventListener('drop', async e => { + + + + + + diff --git a/src/tweeq/FloatingPane/FloatingPane.vue b/src/tweeq/FloatingPane/FloatingPane.vue index 6e3fc79..34202e9 100644 --- a/src/tweeq/FloatingPane/FloatingPane.vue +++ b/src/tweeq/FloatingPane/FloatingPane.vue @@ -305,7 +305,6 @@ onMounted(() => { .left display none - .resize position absolute hover-transition() @@ -380,4 +379,3 @@ onMounted(() => { height 100% hover-transition(opacity) -@/tweaq/useAppStorage diff --git a/src/tweeq/GlslCanvas.vue b/src/tweeq/GlslCanvas.vue index 20c1179..17670da 100644 --- a/src/tweeq/GlslCanvas.vue +++ b/src/tweeq/GlslCanvas.vue @@ -1,87 +1,104 @@ - diff --git a/src/tweeq/InputButton.vue b/src/tweeq/InputButton.vue index b27eddf..2e6a408 100644 --- a/src/tweeq/InputButton.vue +++ b/src/tweeq/InputButton.vue @@ -20,9 +20,12 @@ withDefaults(defineProps(), {label: ''}) background var(--tq-color-primary-container) color var(--md-sys-color-on-primary-container) font-size inherit - hover-transition(background, collor) + hover-transition(background, color) - &:hover, &:focus-visible + &:focus-visible + background var(--tq-color-tinted-input-active) + + &:hover color var(--tq-color-on-primary) background var(--tq-color-primary) diff --git a/src/tweeq/InputCheckbox/InputCheckbox.vue b/src/tweeq/InputCheckbox/InputCheckbox.vue index 59b0f18..60bf362 100644 --- a/src/tweeq/InputCheckbox/InputCheckbox.vue +++ b/src/tweeq/InputCheckbox/InputCheckbox.vue @@ -75,6 +75,7 @@ function onInput(e: InputEvent) { color var(--tq-color-primary) line-height 1em pointer-events none + hover-transition(box-shadow) &__checkmark position relative @@ -100,7 +101,7 @@ function onInput(e: InputEvent) { // Hover and Focus &:hover &__frame, &:focus-within &__frame - box-shadow inset 0 0 0 1px var(--tq-color-primary) + box-shadow 0 0 0 1px var(--tq-color-primary) color var(--tq-color-primary) // Label diff --git a/src/tweeq/InputColor/InputColor.vue b/src/tweeq/InputColor/InputColor.vue new file mode 100644 index 0000000..43cf87c --- /dev/null +++ b/src/tweeq/InputColor/InputColor.vue @@ -0,0 +1,293 @@ + + + + + diff --git a/src/tweeq/InputColor/InputColorChannelPad.vue b/src/tweeq/InputColor/InputColorChannelPad.vue new file mode 100644 index 0000000..62d3591 --- /dev/null +++ b/src/tweeq/InputColor/InputColorChannelPad.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/src/tweeq/InputColor/InputColorChannelSlider.vue b/src/tweeq/InputColor/InputColorChannelSlider.vue new file mode 100644 index 0000000..b8d5146 --- /dev/null +++ b/src/tweeq/InputColor/InputColorChannelSlider.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/tweeq/InputColor/InputColorChannelValues.vue b/src/tweeq/InputColor/InputColorChannelValues.vue new file mode 100644 index 0000000..998b917 --- /dev/null +++ b/src/tweeq/InputColor/InputColorChannelValues.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/src/tweeq/InputColor/InputColorPicker.vue b/src/tweeq/InputColor/InputColorPicker.vue new file mode 100644 index 0000000..e392abd --- /dev/null +++ b/src/tweeq/InputColor/InputColorPicker.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/src/tweeq/InputColor/InputColorPresets.vue b/src/tweeq/InputColor/InputColorPresets.vue new file mode 100644 index 0000000..13a807d --- /dev/null +++ b/src/tweeq/InputColor/InputColorPresets.vue @@ -0,0 +1,46 @@ + + + + + +./useInputColor diff --git a/src/tweeq/InputColor/common.styl b/src/tweeq/InputColor/common.styl new file mode 100644 index 0000000..4118586 --- /dev/null +++ b/src/tweeq/InputColor/common.styl @@ -0,0 +1,20 @@ +@import '../common.styl' + +circle() + position absolute + margin-bottom calc(-0.35 * var(--tq-input-height)) + margin-left calc(-0.35 * var(--tq-input-height)) + width calc(0.7 * var(--tq-input-height)) + height calc(0.7 * var(--tq-input-height)) + border-radius 50% + box-shadow 0 0 0 1.5px #fff, inset 0 0 0px 1px rgba(0, 0, 0, 0.1), 0 0 1px 2px rgba(0, 0, 0, 0.4) + transition transform 0.05s ease + + &.tweaking + transform scale(2) + +background-checkerboard(size = 6px) + background-color white + background-image linear-gradient(45deg, #ddd 25%, transparent 25%), linear-gradient(135deg, #ddd 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ddd 75%), linear-gradient(135deg, transparent 75%, #ddd 75%) + background-position 0 0, size 0, size (-1 * size), 0px size + background-size 2 * size 2 * size \ No newline at end of file diff --git a/src/tweeq/InputColor/index.ts b/src/tweeq/InputColor/index.ts new file mode 100644 index 0000000..cb0c658 --- /dev/null +++ b/src/tweeq/InputColor/index.ts @@ -0,0 +1,4 @@ +import InputColor from './InputColor.vue' +import InputColorPicker from './InputColorPicker.vue' + +export {InputColorPicker, InputColor} diff --git a/src/tweeq/InputColor/pad.frag b/src/tweeq/InputColor/pad.frag new file mode 100644 index 0000000..d7ff9b0 --- /dev/null +++ b/src/tweeq/InputColor/pad.frag @@ -0,0 +1,83 @@ +precision mediump float; + +varying vec2 uv; + +uniform vec4 hsva; +uniform ivec2 axes; + +#define R 0 +#define G 1 +#define B 2 +#define A 3 +#define H 4 +#define S 5 +#define V 6 + +#define NONE -1.0 + +//http://gamedev.stackexchange.com/questions/59797/glsl-shader-change-hue-saturation-brightness +vec3 rgb2hsv(vec3 c) +{ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void main() { + vec3 _hsv = hsva.rgb; + + vec3 rgb = hsv2rgb(_hsv); + vec4 outColor = vec4(rgb, 1.0); + + + float hue = NONE; + float sat = NONE; + + for (int i = 0; i < 2; i++) { + int axis = axes[i]; + float t = uv[i]; + + if (axis == R) { + outColor.r = t; + } else if (axis == G) { + outColor.g = t; + } else if (axis == B) { + outColor.b = t; + } else if (axis == A) { + outColor.a = t; + } else { + vec3 _hsv = rgb2hsv(outColor.rgb); + + if (_hsv[1] == 0.0 || _hsv[2] == 0.0) { + _hsv[0] = hue == NONE ? hsva[0] : hue; + _hsv[1] = sat == NONE ? hsva[1] : sat; + } + + if (axis == H) { + _hsv[0] = t; + hue = t; + } else if (axis == S) { + _hsv[1] = t; + sat = t; + } else if (axis == V) { + _hsv[2] = t; + } + + outColor.rgb = hsv2rgb(_hsv); + } + } + + + + + gl_FragColor = outColor; +} \ No newline at end of file diff --git a/src/tweeq/InputColor/slider.frag b/src/tweeq/InputColor/slider.frag new file mode 100644 index 0000000..87260b4 --- /dev/null +++ b/src/tweeq/InputColor/slider.frag @@ -0,0 +1,62 @@ +precision mediump float; + +varying vec2 uv; + +uniform vec4 hsva; +uniform int axis; +uniform float offset; + +#define R 0 +#define G 1 +#define B 2 +#define A 3 +#define H 4 +#define S 5 +#define V 6 + +//http://gamedev.stackexchange.com/questions/59797/glsl-shader-change-hue-saturation-brightness +vec3 rgb2hsv(vec3 c) +{ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void main() { + float t = uv.x + offset; + + vec4 outColor = vec4(hsv2rgb(hsva.xyz), 1.0); + + outColor.a = 1.0; + + if (axis == R) { + outColor.r = t; + } else if (axis == G) { + outColor.g = t; + } else if (axis == B) { + outColor.b = t; + } else if (axis == A) { + outColor.a = t; + } else { + vec3 _hsv = hsva.xyz; + if (axis == H) { + _hsv = vec3(t, 1.0, 1.0); + } else if (axis == S) { + _hsv[1] = t; + } else if (axis == V) { + _hsv[2] = t; + } + outColor.rgb = hsv2rgb(_hsv); + } + + gl_FragColor = outColor; +} \ No newline at end of file diff --git a/src/tweeq/InputColor/types.ts b/src/tweeq/InputColor/types.ts new file mode 100644 index 0000000..5da1548 --- /dev/null +++ b/src/tweeq/InputColor/types.ts @@ -0,0 +1,150 @@ +export type Color = string +export type ColorChannel = 'r' | 'g' | 'b' | 'a' | 'h' | 's' | 'v' +export type ColorPicker = + | ColorChannel // For slider + | `${ColorChannel}${ColorChannel}` // For 2D pad +export type ColorSpace = 'rgb' | 'hsv' | 'hex' + +export function colorChannelToIndex(channel: ColorChannel): number { + switch (channel) { + case 'r': + return 0 + case 'g': + return 1 + case 'b': + return 2 + case 'a': + return 3 + case 'h': + return 4 + case 's': + return 5 + case 'v': + return 6 + } +} + +export type RGB = [r: number, g: number, b: number] +export type RGBA = [r: number, g: number, b: number, a: number] +export type HSV = [h: number, s: number, v: number] + +export type Channels = { + r: number + g: number + b: number + a: number + h: number + s: number + v: number +} + +export type ColorUIComponent = + | [type: 'slider', axis: ColorChannel] + | [type: 'pad', axes: [ColorChannel, ColorChannel]] + | [type: 'values'] + | [type: 'presets'] + +export type ColorUI = ColorUIComponent[] + +export const DefualtColorUI: ColorUI = [ + ['pad', ['s', 'v']], + ['slider', 'h'], + ['slider', 'a'], + ['values'], +] + +export interface InputColorProps { + modelValue: string + ui?: ColorUIComponent[] + presets?: string[] +} + +// https://gist.github.com/mjackson/5311256 + +/** + * Converts an RGB color value to HSV. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSV_color_space. + * Assumes r, g, and b are contained in the set [0, 1] and + * returns h, s, and v in the set [0, 1]. + * + * @param Number r The red color value + * @param Number g The green color value + * @param Number b The blue color value + * @return Array The HSV representation + */ +export function rgb2hsv([r, g, b]: RGB): HSV { + const max = Math.max(r, g, b) + const min = Math.min(r, g, b) + + const v = max + + const d = max - min + const s = max === 0 ? NaN : d / max + + let h: number + if (max === min) { + h = NaN // achromatic + } else { + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0) + break + case g: + h = (b - r) / d + 2 + break + default: + // case b: + h = (r - g) / d + 4 + break + } + + h /= 6 + } + + return [h, s, v] +} + +/** + * Converts an HSV color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSV_color_space. + * Assumes h, s, and v are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param Number h The hue + * @param Number s The saturation + * @param Number v The value + * @return Array The RGB representation + */ +export function hsv2rgb([h, s, v]: HSV): RGB { + let r, g, b + + const i = Math.floor(h * 6) + const f = h * 6 - i + const p = v * (1 - s) + const q = v * (1 - f * s) + const t = v * (1 - (1 - f) * s) + + switch (i % 6) { + case 0: + ;(r = v), (g = t), (b = p) + break + case 1: + ;(r = q), (g = v), (b = p) + break + case 2: + ;(r = p), (g = v), (b = t) + break + case 3: + ;(r = p), (g = q), (b = v) + break + case 4: + ;(r = t), (g = p), (b = v) + break + default: + // case 5: + ;(r = v), (g = p), (b = q) + break + } + + return [r, g, b] +} diff --git a/src/tweeq/InputColor/useInputColor.ts b/src/tweeq/InputColor/useInputColor.ts new file mode 100644 index 0000000..41d8299 --- /dev/null +++ b/src/tweeq/InputColor/useInputColor.ts @@ -0,0 +1,18 @@ +import {InjectionKey, provide, Ref, ref} from 'vue' + +import {ColorSpace} from './types' + +export const InputColorPresetsKey: InjectionKey = Symbol( + 'InputColorPresetsKey' +) + +export const InputColorSpaceKey: InjectionKey> = + Symbol('InputColorSpaceKey') + +export function useInputColor() { + provide(InputColorPresetsKey, ['skyblue', 'tomato']) + + const colorSpace = ref('hsv') + + provide(InputColorSpaceKey, colorSpace) +} diff --git a/src/tweeq/InputColorPicker/InputColorPicker.vue b/src/tweeq/InputColorPicker/InputColorPicker.vue deleted file mode 100644 index 38b77c0..0000000 --- a/src/tweeq/InputColorPicker/InputColorPicker.vue +++ /dev/null @@ -1,149 +0,0 @@ - - - - - diff --git a/src/tweeq/InputColorPicker/SliderAlpha.vue b/src/tweeq/InputColorPicker/SliderAlpha.vue deleted file mode 100644 index ee659e3..0000000 --- a/src/tweeq/InputColorPicker/SliderAlpha.vue +++ /dev/null @@ -1,114 +0,0 @@ - - - - - diff --git a/src/tweeq/InputColorPicker/SliderHSV.vue b/src/tweeq/InputColorPicker/SliderHSV.vue deleted file mode 100644 index 38725a1..0000000 --- a/src/tweeq/InputColorPicker/SliderHSV.vue +++ /dev/null @@ -1,218 +0,0 @@ - - - - - diff --git a/src/tweeq/InputColorPicker/SliderHSVRadial.vue b/src/tweeq/InputColorPicker/SliderHSVRadial.vue deleted file mode 100644 index ce55ef9..0000000 --- a/src/tweeq/InputColorPicker/SliderHSVRadial.vue +++ /dev/null @@ -1,198 +0,0 @@ - - - - - diff --git a/src/tweeq/InputColorPicker/SliderRGB.vue b/src/tweeq/InputColorPicker/SliderRGB.vue deleted file mode 100644 index 01eecab..0000000 --- a/src/tweeq/InputColorPicker/SliderRGB.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - - - diff --git a/src/tweeq/InputColorPicker/common.styl b/src/tweeq/InputColorPicker/common.styl deleted file mode 100644 index 64c7836..0000000 --- a/src/tweeq/InputColorPicker/common.styl +++ /dev/null @@ -1,17 +0,0 @@ -@import '../common.styl' - -$circle-diameter = calc(0.7 * var(--tq-input-height)) -$circle-radius = calc(0.35 * var(--tq-input-height)) -$picker-gap = calc(0.3 * var(--tq-input-height)) - -circle() - position absolute - margin-left calc(-0.35 * var(--tq-input-height)) - width $circle-diameter - height $circle-diameter - border-radius 50% - box-shadow 0 0 0 1.5px #fff, inset 0 0 0px 1px rgba(0, 0, 0, 0.1), 0 0 1px 2px rgba(0, 0, 0, 0.4) - transition transform 0.03s ease - - &.tweaking - transform scale(2) diff --git a/src/tweeq/InputColorPicker/index.ts b/src/tweeq/InputColorPicker/index.ts deleted file mode 100644 index 629c0b5..0000000 --- a/src/tweeq/InputColorPicker/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -import InputColorPicker from './InputColorPicker.vue' -export default InputColorPicker diff --git a/src/tweeq/InputColorPicker/picker-hsv-pad.frag b/src/tweeq/InputColorPicker/picker-hsv-pad.frag deleted file mode 100644 index 916e670..0000000 --- a/src/tweeq/InputColorPicker/picker-hsv-pad.frag +++ /dev/null @@ -1,50 +0,0 @@ -precision mediump float; - -varying vec2 uv; - -uniform float modeX; -uniform float modeY; -uniform vec3 hsv; -#define MODE_H 0.0 -#define MODE_S 1.0 -#define MODE_V 2.0 - -vec3 hsv2rgb(vec3 c) { - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); -} - -void main() { - vec3 _hsv = vec3(hsv); - - if (modeX == MODE_H) { - _hsv.x = uv.x; - if (modeY == MODE_S) { - _hsv.z = 1.0; - } else if (modeY == MODE_V) { - _hsv.y = 1.0; - } - } else if (modeX == MODE_S) { - _hsv.y = uv.x; - } else if (modeX == MODE_V) { - _hsv.z = uv.x; - } - - if (modeY == MODE_H) { - _hsv.x = uv.y; - if (modeX == MODE_S) { - _hsv.z = 1.0; - } else if (modeX == MODE_V) { - _hsv.y = 1.0; - } - } else if (modeY == MODE_S) { - _hsv.y = uv.y; - } else if (modeY == MODE_V) { - _hsv.z = uv.y; - } - - vec3 color = hsv2rgb(_hsv); - - gl_FragColor = vec4(color, 1.0); -} \ No newline at end of file diff --git a/src/tweeq/InputColorPicker/picker-hsv-radial.frag b/src/tweeq/InputColorPicker/picker-hsv-radial.frag deleted file mode 100644 index 30411f3..0000000 --- a/src/tweeq/InputColorPicker/picker-hsv-radial.frag +++ /dev/null @@ -1,28 +0,0 @@ -precision mediump float; - -varying vec2 uv; -uniform vec3 hsv; - -vec3 hsv2rgb(vec3 c) { - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); -} - -#define PI 3.1415926535897 - -float atan2(in float y, in float x){ - return x == 0.0 ? sign(y) * PI / 2.0 : atan(y, x); -} - -void main() { - vec2 pos = (1.0 - uv) * 2.0 - 1.0; - - float h = (atan2(pos.y, pos.x) / PI + 1.0) / 2.0; - float s = length(pos); - float v = 1.0; - - vec3 color = hsv2rgb(vec3(h, s, v)); - - gl_FragColor = vec4(color, 1.0); -} \ No newline at end of file diff --git a/src/tweeq/InputColorPicker/picker-hsv-slider.frag b/src/tweeq/InputColorPicker/picker-hsv-slider.frag deleted file mode 100644 index c1061bc..0000000 --- a/src/tweeq/InputColorPicker/picker-hsv-slider.frag +++ /dev/null @@ -1,32 +0,0 @@ -precision mediump float; - -varying vec2 uv; - -uniform vec3 hsv; -uniform float mode; -#define MODE_H 0.0 -#define MODE_S 1.0 -#define MODE_V 2.0 -uniform float offset; - -vec3 hsv2rgb(vec3 c) { - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); -} - -void main() { - float t = uv.x + offset; - - vec3 color; - - if (mode == MODE_H) { - color = hsv2rgb(vec3(t, 1.0, 1.0)); - } else if (mode == MODE_S) { - color = hsv2rgb(vec3(hsv.x, t, hsv.z)); - } else if (mode == MODE_V) { - color = hsv2rgb(vec3(hsv.x, hsv.y, t)); - } - - gl_FragColor = vec4(color, 1.0); -} \ No newline at end of file diff --git a/src/tweeq/InputColorPicker/use-hsv.ts b/src/tweeq/InputColorPicker/use-hsv.ts deleted file mode 100644 index 103a407..0000000 --- a/src/tweeq/InputColorPicker/use-hsv.ts +++ /dev/null @@ -1,66 +0,0 @@ -import chroma from 'chroma-js' -import {Ref} from 'vue' - -import useSurjective from '../useSurjective' - -export type HSV = {h: number; s: number; v: number} -export type HSVA = HSV & {a: number} -export type RGB = {r: number; g: number; b: number} -export type RGBA = RGB & {a: number} - -export function color2rgba(value: string, useAlpha: boolean): RGBA | null { - const dict: RGBA = {r: 1, g: 1, b: 1, a: 1} - - if (!chroma.valid(value)) { - return null - } - - const c = chroma(value) - - const [r, g, b] = c.rgb() - dict.r = r / 255 - dict.g = g / 255 - dict.b = b / 255 - - if (useAlpha) { - dict.a = c.alpha() - } - - return dict -} - -export function rgba2color(dict: RGBA, useAlpha: boolean): string { - const c = chroma( - dict.r * 255 ?? 0, - dict.g * 255 ?? 0, - dict.b * 255 ?? 0 - ).alpha(useAlpha ? dict.a : 1) - return c.hex() -} - -export function rgb2hsv({r, g, b}: RGB): HSV { - const [h, s, v] = chroma.rgb(r * 255, g * 255, b * 255).hsv() - return {h: isNaN(h) ? 0 : h / 360, s, v} -} - -export function hsv2rgb({h, s, v}: HSV): RGB { - const [r, g, b] = chroma.hsv(h * 360, s, v).rgba() - return {r: r / 255, g: g / 255, b: b / 255} -} - -export function equalColor(x: RGB, y: RGB) { - return x.r === y.r && x.g === y.g && x.b === y.b -} - -export function hsv2color({h, s, v}: HSV) { - return chroma.hsv(h * 360, s, v).hex() -} - -export default function useHSV(rgb: Ref) { - const state = useSurjective(rgb, rgb2hsv, hsv2rgb, equalColor) - - return { - hsv: state.y, - hsv2rgb: state.inverse, - } -} diff --git a/src/tweeq/InputCubicBezier/InputCubicBezier.vue b/src/tweeq/InputCubicBezier/InputCubicBezier.vue index cfa0db6..523899c 100644 --- a/src/tweeq/InputCubicBezier/InputCubicBezier.vue +++ b/src/tweeq/InputCubicBezier/InputCubicBezier.vue @@ -1,40 +1,18 @@ - - - diff --git a/src/tweeq/InputCubicBezier/InputCubicBezierPicker.vue b/src/tweeq/InputCubicBezier/InputCubicBezierPicker.vue index 19a22ab..0596aea 100644 --- a/src/tweeq/InputCubicBezier/InputCubicBezierPicker.vue +++ b/src/tweeq/InputCubicBezier/InputCubicBezierPicker.vue @@ -1,107 +1,102 @@ - - - - diff --git a/src/tweeq/InputCubicBezier/index.ts b/src/tweeq/InputCubicBezier/index.ts index 90dfab4..893796b 100644 --- a/src/tweeq/InputCubicBezier/index.ts +++ b/src/tweeq/InputCubicBezier/index.ts @@ -1,3 +1,4 @@ import InputCubicBezier from './InputCubicBezier.vue' +import InputCubicBezierPicker from './InputCubicBezierPicker.vue' -export default InputCubicBezier +export {InputCubicBezier, InputCubicBezierPicker} diff --git a/src/tweeq/InputCubicBezier/types.ts b/src/tweeq/InputCubicBezier/types.ts deleted file mode 100644 index c6691a2..0000000 --- a/src/tweeq/InputCubicBezier/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type CubicBezier = [number, number, number, number] diff --git a/src/tweeq/InputCubicBezier/util.ts b/src/tweeq/InputCubicBezier/util.ts new file mode 100644 index 0000000..0b8e001 --- /dev/null +++ b/src/tweeq/InputCubicBezier/util.ts @@ -0,0 +1 @@ +export type CubicBezierValue = readonly [number, number, number, number] diff --git a/src/tweeq/InputDropdown.vue b/src/tweeq/InputDropdown.vue index b6b6fe0..e5250b1 100644 --- a/src/tweeq/InputDropdown.vue +++ b/src/tweeq/InputDropdown.vue @@ -6,26 +6,20 @@ import {computed, Ref, ref, watch} from 'vue' import InputString from './InputString' import Popover from './Popover.vue' import SvgIcon from './SvgIcon.vue' +import {InputTheme, Labelizer, useLabelizer} from './types' import {unsignedMod} from './util' -type Labelizer = (v: T) => string - interface Props { modelValue: T - items: T[] + options: T[] labels?: string[] - labelize?: Labelizer + labelizer?: Labelizer + theme?: InputTheme } -const props = defineProps() +const props = withDefaults(defineProps(), {}) -const labelize = computed(() => { - if (props.labelize) return props.labelize - return (v: T) => { - const index = props.items.indexOf(v) - return props.labels?.[index] ?? String(v) - } -}) +const labelizer = useLabelizer(props) const emit = defineEmits<{ 'update:modelValue': [T] @@ -41,14 +35,14 @@ const $root = ref(null) const {width: inputWidth} = useElementSize($root) const startValue = ref(props.modelValue) as Ref -const display = ref(labelize.value(props.modelValue)) +const display = ref(labelizer.value(props.modelValue)) const displayEdited = ref(false) watch( () => [open.value, props.modelValue] as const, ([open, modelValue]) => { if (open) return - display.value = labelize.value(modelValue) + display.value = labelizer.value(modelValue) displayEdited.value = false }, {flush: 'post'} @@ -60,17 +54,17 @@ watch(open, (open, oldOpen) => { } }) -const filteredItems = computed(() => { - if (!display.value || !displayEdited.value) return props.items +const filteredOptions = computed(() => { + if (!display.value || !displayEdited.value) return props.options - return search(display.value, props.items as any[], { - keySelector: labelize.value, + return search(display.value, props.options as any[], { + keySelector: labelizer.value, }) as T[] }) -watch(filteredItems, items => { - if (items.length === 1 || !items.includes(props.modelValue)) { - emit('update:modelValue', items[0]) +watch(filteredOptions, options => { + if (!options.includes(props.modelValue)) { + emit('update:modelValue', options[0]) } }) @@ -82,11 +76,11 @@ function onBlur() { open.value = false } -function onSelect(item: T, e: PointerEvent) { +function onSelect(option: T, e: PointerEvent) { if (e.type === 'pointerdown' && e.isPrimary) { open.value = false } - emit('update:modelValue', item) + emit('update:modelValue', option) } function onUnselect() { @@ -94,11 +88,11 @@ function onUnselect() { } function onPressArrow(isUp: boolean) { - const length = filteredItems.value.length - const index = filteredItems.value.indexOf(props.modelValue) + const length = filteredOptions.value.length + const index = filteredOptions.value.indexOf(props.modelValue) const newIndex = unsignedMod(index + (isUp ? -1 : 1), length) - const item = filteredItems.value[newIndex] - emit('update:modelValue', item) + const option = filteredOptions.value[newIndex] + emit('update:modelValue', option) } @@ -106,6 +100,7 @@ function onPressArrow(isUp: boolean) {
  • - {{ labelize(item) }} + {{ labelizer(item) }}
  • @@ -164,7 +159,6 @@ $right-arrow-width = 1em &.open .input background var(--tq-color-primary-container) - .select margin 1px padding 0 @@ -177,12 +171,12 @@ $right-arrow-width = 1em height var(--tq-input-height) line-height var(--tq-input-height) -.option.startValue - background var(--tq-color-primary-container) + &.startValue + background var(--tq-color-primary-container) -.option.active - background var(--tq-color-primary) - color var(--tq-color-on-primary) + &.active + background var(--tq-color-primary) + color var(--tq-color-on-primary) .chevron position absolute @@ -193,6 +187,6 @@ $right-arrow-width = 1em transform-origin 50% 50% pointer-events none fill none - stroke var(--tq-color-primary) + stroke var(--md-sys-color-outline-variant) hover-transition(transform) diff --git a/src/tweeq/InputNumber/InputNumber.vue b/src/tweeq/InputNumber/InputNumber.vue index 257d45c..8b3b741 100644 --- a/src/tweeq/InputNumber/InputNumber.vue +++ b/src/tweeq/InputNumber/InputNumber.vue @@ -2,8 +2,9 @@ import {useElementBounding, useFocus, useKeyModifier} from '@vueuse/core' import {useWheel} from '@vueuse/gesture' import {scalar, Vec2} from 'linearly' -import {computed, ref, watch, watchEffect} from 'vue' +import {computed, ref, watch} from 'vue' +import {InputHorizontalPosition, InputVerticalPosition} from '../types' import {useDrag} from '../useDrag' import {toFixed, unsignedMod} from '../util' @@ -11,19 +12,25 @@ interface Props { modelValue: number min?: number max?: number + bar?: boolean clampMin?: boolean clampMax?: boolean invalid?: boolean disabled?: boolean precision?: number + unit?: string + horizontalPosition?: InputHorizontalPosition + verticalPosition?: InputVerticalPosition } const props = withDefaults(defineProps(), { min: Number.MIN_SAFE_INTEGER, max: Number.MAX_SAFE_INTEGER, + bar: true, clampMin: true, clampMax: true, precision: 4, + unit: '', }) const emit = defineEmits<{ @@ -34,7 +41,7 @@ const root = ref(null) const input = ref(null) const local = ref(props.modelValue) -const display = ref(toFixed(props.modelValue, props.precision)) +const display = ref(toFixed(props.modelValue, props.precision) + props.unit) const {left, top, width, height, right} = useElementBounding(root) @@ -43,6 +50,7 @@ const shift = useKeyModifier('Shift') const hasRange = computed(() => { return ( + props.bar && props.min !== Number.MIN_SAFE_INTEGER && props.max !== Number.MAX_SAFE_INTEGER ) @@ -158,7 +166,7 @@ const {dragging: tweaking, pointerLocked} = useDrag(root, { } }, onDragEnd() { - display.value = toFixed(props.modelValue, tweakPrecision.value) + display.value = toFixed(props.modelValue, tweakPrecision.value) + props.unit tweakMode.value = null speedMultiplierGesture.value = 1 }, @@ -170,7 +178,7 @@ useWheel( local.value = props.modelValue + y * speedMultiplierGesture.value local.value = scalar.clamp(local.value, validMin.value, validMax.value) - display.value = props.modelValue.toFixed(tweakPrecision.value) + display.value = props.modelValue.toFixed(tweakPrecision.value) + props.unit emit('update:modelValue', local.value) }, @@ -180,11 +188,14 @@ useWheel( let hasChanged = false let initialDisplay = '' -const onFocus = (e: FocusEvent) => { - const el = e.target as HTMLInputElement - el.select() +const focusing = useFocus(input).focused + +const onFocus = () => { hasChanged = false initialDisplay = display.value + + // TODO: This is a hack to make the input element select all text + setTimeout(() => input.value?.select(), 16) } const onInput = (e: Event) => { @@ -208,27 +219,41 @@ const increment = (delta: number) => { ) local.value += delta * speedMultiplierKey.value local.value = scalar.clamp(local.value, validMin.value, validMax.value) - display.value = toFixed(local.value, prec) + display.value = toFixed(local.value, prec) + props.unit hasChanged = true emit('update:modelValue', local.value) } const onBlur = () => { if (hasChanged) { - display.value = toFixed(props.modelValue, props.precision) + display.value = toFixed(props.modelValue, props.precision) + props.unit } else { // 変な文字を打ったときはhasChanged === falseなので、これでリセットをかける display.value = initialDisplay } } -watchEffect(() => { - if (tweaking.value) { - display.value = props.modelValue.toFixed(tweakPrecision.value) - } -}) +watch( + () => + [ + props.modelValue, + tweaking.value, + focusing.value, + tweakPrecision.value, + ] as const, + ([modelValue, tweaking, focusing]) => { + if (tweaking) { + display.value = modelValue.toFixed(tweakPrecision.value) + props.unit + } else if (focusing) { + display.value = toFixed(modelValue, props.precision) + } else { + display.value = modelValue.toFixed(props.precision) + props.unit + } + }, + {flush: 'sync'} +) -const scaleOffset = ref(0) +const scaleOffset = ref(0.0) const scaleAttrs = (offset: number) => { const precision = unsignedMod( @@ -312,7 +337,14 @@ window.addEventListener('touchstart', (e: TouchEvent) => {