feat: color picker

This commit is contained in:
aarthificial
2022-03-24 19:07:43 +01:00
parent d201a4d093
commit ac48055b4f
6 changed files with 221 additions and 28 deletions

View File

@@ -52,7 +52,7 @@ export function surfaceTransition(fromSurfaceOriginal: Surface) {
...from,
...value.rectArc(from, to, config.reverse),
radius: value.easeInOutCubic(from.radius, to.radius),
color: value.color(from.color, to.color, value.easeInOutQuint()),
color: value.color(from.color, target.getChild().getColor(), value.easeInOutQuint()),
});
target.setPosition(value.rectArc(fromNewPos, toPos, config.reverse));
if (!config.onToOpacityChange?.(target, value)) {
@@ -70,7 +70,7 @@ export function surfaceTransition(fromSurfaceOriginal: Surface) {
...from,
...value.rectArc(from, to, config.reverse),
radius: value.easeInOutCubic(from.radius, to.radius),
color: value.color(from.color, to.color, value.easeInOutQuint()),
color: value.color(from.color, target.getChild().getColor(), value.easeInOutQuint()),
});
fromSurface.setPosition(
value.rectArc(fromPos, toNewPos, config.reverse),

View File

@@ -0,0 +1,103 @@
import {LinearLayout, LinearLayoutConfig} from 'MC/components/LinearLayout';
import {Rect} from 'konva/lib/shapes/Rect';
import {Range, RangeConfig} from 'MC/components/Range';
import {Node} from 'konva/lib/Node';
import {GetSet} from 'konva/lib/types';
import {getset} from 'MC/decorators';
import mixColor, {parseColor} from "mix-color";
import {TimeTween} from "MC/animations";
export interface ColorPickerConfig extends LinearLayoutConfig {
previewColor?: string;
dissolve?: number;
}
const colorRangeConfig: RangeConfig = {
width: 280,
height: 60,
range: [0, 255],
precision: 0,
margin: 10,
value: 0,
};
export class ColorPicker extends LinearLayout {
@getset('#000000', ColorPicker.prototype.updateColor)
public previewColor: GetSet<ColorPickerConfig['previewColor'], this>;
@getset(0, ColorPicker.prototype.updateDissolve)
public dissolve: GetSet<ColorPickerConfig['dissolve'], this>;
public readonly preview: Rect;
public readonly r: Range;
public readonly g: Range;
public readonly b: Range;
public readonly a: Range;
public constructor(config?: ColorPickerConfig) {
super(config);
this.preview = new Rect({
width: 360,
height: 200,
fill: 'yellow',
cornerRadius: [8, 8, 0, 0],
});
this.r = new Range({
...colorRangeConfig,
label: 'R:',
margin: [40, 10, 10],
});
this.g = new Range({
...colorRangeConfig,
label: 'G:',
});
this.b = new Range({
...colorRangeConfig,
label: 'B:',
});
this.a = new Range({
...colorRangeConfig,
label: 'A:',
margin: [10, 10, 40],
});
this.add(this.preview, this.r, this.g, this.b, this.a);
this.updateColor();
this.updateDissolve();
}
private updateColor() {
const color = parseColor(this.previewColor());
color.a = Math.round(color.a * 255);
this.r.value(color.r);
this.g.value(color.g);
this.b.value(color.b);
this.a.value(color.a);
this.preview.fill(this.previewColor());
}
private updateDissolve() {
if (!this.r) return;
const opacity = TimeTween.clampRemap(0, 0.5, 1, 0, this.dissolve());
this.r.opacity(opacity);
this.g.opacity(opacity);
this.b.opacity(opacity);
this.a.opacity(opacity);
this.fireLayoutChange();
}
getColor(): string {
return mixColor(
super.getColor(),
this.previewColor(),
TimeTween.clampRemap(0.5, 1, 0, 1, this.dissolve()),
);
}
clone(obj?: any): this {
return Node.prototype.clone.call(this, obj);
}
}

View File

@@ -3,12 +3,11 @@ import {GetSet, IRect} from 'konva/lib/types';
import {Factory} from 'konva/lib/Factory';
import {Shape} from 'konva/lib/Shape';
import {LayoutGroup, LayoutGroupConfig} from './LayoutGroup';
import {LayoutShape} from './LayoutShape';
import {Center, Origin, Size} from '../types';
import {Center, Origin, Size, Spacing} from '../types';
import {Container} from 'konva/lib/Container';
import {getClientRect} from "MC/components/ILayoutNode";
import {getClientRect, getOriginDelta, isLayoutNode} from "MC/components/ILayoutNode";
export interface LayoutConfig extends LayoutGroupConfig {
export interface LinearLayoutConfig extends LayoutGroupConfig {
direction?: Center;
}
@@ -16,7 +15,7 @@ export class LinearLayout extends LayoutGroup {
public direction: GetSet<Center, this>;
private contentSize: Size;
constructor(config?: LayoutConfig) {
constructor(config?: LinearLayoutConfig) {
super(config);
}
@@ -38,14 +37,11 @@ export class LinearLayout extends LayoutGroup {
if (!this.children) return;
this.contentSize = {width: 0, height: 0};
const children = this.children.filter<LayoutGroup | LayoutShape>(
(child): child is LayoutGroup | LayoutShape =>
child instanceof LayoutGroup || child instanceof LayoutShape,
);
for (const child of children) {
const size = child.getLayoutSize();
const margin = child.getMargin();
for (const child of this.children) {
const isLayout = isLayoutNode(child);
const size = isLayout ? child.getLayoutSize() : child.getSize();
const margin = isLayout ? child.getMargin() : new Spacing();
const scale = child.getAbsoluteScale(this);
this.contentSize.width = Math.max(
this.contentSize.width,
@@ -55,11 +51,12 @@ export class LinearLayout extends LayoutGroup {
}
let height = this.contentSize.height / -2;
for (const child of children) {
const size = child.getLayoutSize();
const margin = child.getMargin();
for (const child of this.children) {
const isLayout = isLayoutNode(child);
const size = isLayout ? child.getLayoutSize() : child.getSize();
const margin = isLayout ? child.getMargin() : new Spacing();
const offset = isLayout ? child.getOriginDelta(Origin.Top) : getOriginDelta(size, Origin.TopLeft, Origin.Top);
const scale = child.getAbsoluteScale(this);
const offset = child.getOriginDelta(Origin.Top);
child.position({
x: -offset.x * scale.x,

92
src/components/Range.ts Normal file
View File

@@ -0,0 +1,92 @@
import {LayoutShape, LayoutShapeConfig} from './LayoutShape';
import {Context} from 'konva/lib/Context';
import {GetSet} from 'konva/lib/types';
import {getset, KonvaNode} from 'MC/decorators';
import {CanvasHelper} from 'MC/helpers';
import {TimeTween} from 'MC/animations';
export interface RangeConfig extends LayoutShapeConfig {
range?: [number, number];
value?: number;
precision?: number;
backgroundColor?: string;
foregroundColor?: string;
textColor?: string;
label?: string;
}
@KonvaNode()
export class Range extends LayoutShape {
@getset([0, 1])
public range: GetSet<RangeConfig['range'], this>;
@getset(0.5)
public value: GetSet<RangeConfig['value'], this>;
@getset(0)
public precision: GetSet<RangeConfig['precision'], this>;
@getset('#141414')
public backgroundColor: GetSet<RangeConfig['backgroundColor'], this>;
@getset('rgba(255, 255, 255, 0.24)')
public foregroundColor: GetSet<RangeConfig['foregroundColor'], this>;
@getset('rgba(255, 255, 255, 0.54')
public textColor: GetSet<RangeConfig['textColor'], this>;
@getset(null)
public label: GetSet<RangeConfig['label'], this>;
public constructor(config?: RangeConfig) {
super(config);
}
public _sceneFunc(context: Context) {
const ctx = context._context;
const size = this.getSize();
const value = this.value();
const range = this.range();
const precision = this.precision();
const label = this.label();
const text = value.toLocaleString('en-EN', {
minimumFractionDigits: precision,
maximumFractionDigits: precision,
});
const position = {
x: size.width / -2,
y: size.height / -2,
}
if (label) {
position.x += 60;
size.width -= 60;
}
ctx.fillStyle = this.backgroundColor();
CanvasHelper.roundRect(
ctx,
position.x,
position.y,
size.width,
size.height,
8,
);
ctx.fill();
const width = TimeTween.remap(range[0], range[1], 16, size.width, value);
ctx.fillStyle = this.foregroundColor();
CanvasHelper.roundRect(
ctx,
position.x,
position.y,
width,
size.height,
8,
);
ctx.fill();
ctx.fillStyle = this.textColor();
ctx.font = 'bold 28px "JetBrains Mono"';
ctx.fillText(text, position.x + 20, 10);
if (label) {
ctx.fillText(label, position.x - 60, 10);
}
}
}

View File

@@ -2,6 +2,6 @@ import {Factory} from "konva/lib/Factory";
export function getset(defaultValue?: any, after?: () => void): PropertyDecorator {
return function (target, propertyKey) {
Factory.addGetterSetter(target.constructor, propertyKey, defaultValue, after);
Factory.addGetterSetter(target.constructor, propertyKey, defaultValue, undefined, after);
};
}

View File

@@ -1,4 +1,5 @@
import {Context} from 'konva/lib/Context';
import {PossibleSpacing, Spacing} from "MC/types";
export namespace CanvasHelper {
export function roundRect<T extends CanvasRenderingContext2D | Context>(
@@ -7,10 +8,8 @@ export namespace CanvasHelper {
y: number,
width: number,
height: number,
radius: number,
radius: PossibleSpacing,
): T {
if (width < 2 * radius) radius = width / 2;
if (height < 2 * radius) radius = height / 2;
ctx.beginPath();
roundRectPath(ctx, x, y, width, height, radius);
ctx.closePath();
@@ -26,13 +25,15 @@ export namespace CanvasHelper {
y: number,
width: number,
height: number,
radius: number,
radius: PossibleSpacing,
): T {
ctx.moveTo(x + radius, y);
ctx.arcTo(x + width, y, x + width, y + height, radius);
ctx.arcTo(x + width, y + height, x, y + height, radius);
ctx.arcTo(x, y + height, x, y, radius);
ctx.arcTo(x, y, x + width, y, radius);
//FIXME Handle too small radii
const spacing = new Spacing(radius);
ctx.moveTo(x + spacing.left, y);
ctx.arcTo(x + width, y, x + width, y + height, spacing.top);
ctx.arcTo(x + width, y + height, x, y + height, spacing.right);
ctx.arcTo(x, y + height, x, y, spacing.bottom);
ctx.arcTo(x, y, x + width, y, spacing.left);
return ctx;
}