Skip to content
Snippets Groups Projects
Commit ac9f69ff authored by Michal Malý's avatar Michal Malý
Browse files

ReDNATCO plugin stage 3

parent c6ff8e6f
No related branches found
No related tags found
No related merge requests found
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Lada Biedermannová <Lada.Biedermannova@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Bohdan Schneider <Bohdan.Schneider@ibt.cas.cz>
*/
import React from 'react';
import ReactDOM from 'react-dom';
import { Colors } from './colors';
import { PushButton, SpinBox } from './controls';
import { parseInt as parseIntMS } from '../../mol-io/reader/common/text/number-parser';
const PALETTE_CURSOR_HALFSIZE = 10;
const VALUE_CURSOR_THICKNESS = 3;
const MIN_RGB = 0;
const MAX_RGB = 255;
const MIN_HUE = 0;
const MAX_HUE = 359;
const MIN_SATVAL = 0;
const MAX_SATVAL = 100;
function isRgbVal(v: number) {
if (isNaN(v))
return false;
return v >= MIN_RGB && v <= MAX_RGB;
}
function isHueVal(v: number) {
if (isNaN(v))
return false;
return v >= MIN_HUE && v <= MAX_HUE;
}
function isSatValVal(v: number) {
if (isNaN(v))
return false;
return v >= MIN_SATVAL && v <= MAX_SATVAL;
}
function stoi(s: string) {
return parseIntMS(s, 0, s.length);
}
interface State {
h: number;
s: number;
v: number;
restoreOnCancel: boolean;
hIn: string;
sIn: string;
vIn: string;
rIn: string;
gIn: string;
bIn: string;
}
export class ColorPicker extends React.Component<ColorPicker.Props, State> {
private paletteRef: React.RefObject<HTMLCanvasElement>;
private valueColumnRef: React.RefObject<HTMLCanvasElement>;
private selfRef: React.RefObject<HTMLDivElement>;
private mouseListenerAttached: boolean;
private touchListenerAttached: boolean;
private lastPaletteX: number;
private lastPaletteY: number;
private lastValueY: number;
constructor(props: ColorPicker.Props) {
super(props);
this.paletteRef = React.createRef();
this.valueColumnRef = React.createRef();
this.selfRef = React.createRef();
this.mouseListenerAttached = false;
this.touchListenerAttached = false;
this.lastPaletteX = 0;
this.lastPaletteY = 0;
this.lastValueY = 0;
const { h, s, v } = Colors.colorToHsv(this.props.initialColor);
const { r, g, b } = Colors.colorToRgb(this.props.initialColor);
this.state = {
h: Math.round(h),
s,
v,
restoreOnCancel: false,
hIn: h.toString(),
sIn: v.toString(),
vIn: s.toString(),
rIn: r.toString(),
gIn: g.toString(),
bIn: b.toString(),
};
}
private calcLeft() {
const self = this.selfRef.current;
if (!self)
return this.props.left;
const bw = document.body.clientWidth;
const right = self.offsetLeft + self.clientWidth;
const overhang = right - bw;
if (overhang > 0)
return bw - self.clientWidth - 25;
return self.offsetLeft;
}
private calcTop() {
const self = this.selfRef.current;
if (!self)
return this.props.top;
const bh = document.body.clientHeight;
const bottom = self.offsetTop + self.clientHeight;
const overhang = bottom - bh;
if (overhang > 0)
return bh - self.clientHeight - 25;
return self.offsetTop;
}
private changeColorFromPalette(ex: number, ey: number) {
const tainer = this.selfRef.current!;
const palette = this.paletteRef.current!;
let x = ex - tainer.offsetLeft - tainer.clientLeft - palette.offsetLeft - palette.clientLeft;
let y = ey - tainer.offsetTop - tainer.clientTop - palette.offsetTop - palette.clientTop;
if (x < 0)
x = 0;
else if (x >= palette.width)
x = palette.width - 1;
if (y < 0)
y = 0;
else if (y >= palette.height)
y = palette.height - 1;
const { h, s } = this.paletteCoordsToHueSat(x, y);
this.updateColorHsv({ h, s, v: this.state.v });
}
private changeColorFromValue(ey: number) {
const tainer = this.selfRef.current!;
const valCol = this.valueColumnRef.current!;
let y = ey - tainer.offsetTop - tainer.clientTop - valCol.offsetTop - valCol.clientTop;
if (y < 0)
y = 0;
else if (y >= valCol.height)
y = valCol.height - 1;
const v = this.valueColumnCoordToVal(y);
this.updateColorHsv({ h: this.state.h, s: this.state.s, v });
}
private dispose() {
document.body.removeChild(this.props.parentElement);
}
private drawPalette() {
if (!this.paletteRef.current)
return;
const canvas = this.paletteRef.current;
const ctx = canvas.getContext('2d');
if (!ctx)
return;
this.drawPalleteInternal(ctx, canvas.width, canvas.height);
this.drawPaletteCursorInternal(ctx, canvas.width, canvas.height, this.state.h, this.state.s);
}
private drawPalleteInternal(ctx: CanvasRenderingContext2D, width: number, height: number) {
const hueStep = 360 / width;
const satStep = 1.0 / height;
ctx.clearRect(0, 0, width, height);
for (let x = 0; x < width; x++) {
const hue = hueStep * x;
for (let y = 0; y < height; y++) {
const sat = 1.0 - satStep * y;
ctx.fillStyle = Colors.hsvToHexString(hue, sat, 1.0);
ctx.fillRect(x, y, 1, 1);
}
}
}
private drawPaletteCursor(hue: number, sat: number) {
if (!this.paletteRef.current)
return;
const canvas = this.paletteRef.current;
const ctx = canvas.getContext('2d');
if (!ctx)
return;
this.drawPaletteCursorInternal(ctx, canvas.width, canvas.height, hue, sat);
}
private drawPaletteCursorInternal(ctx: CanvasRenderingContext2D, width: number, height: number, hue: number, sat: number) {
const hueStep = 360 / width;
const satStep = 1.0 / height;
const fullSize = Math.floor(PALETTE_CURSOR_HALFSIZE * 2);
let fx = Math.floor(this.lastPaletteX - PALETTE_CURSOR_HALFSIZE - 1);
if (fx < 0) fx = 0;
let tx = fx + fullSize + 2;
if (tx > width) tx = width;
let fy = Math.floor(this.lastPaletteY - PALETTE_CURSOR_HALFSIZE - 1);
if (fy < 0) fy = 0;
let ty = fy + fullSize + 2;
if (ty > height) ty = height;
for (let x = fx; x < tx; x++) {
const hue = hueStep * x;
for (let y = fy; y < ty; y++) {
const sat = 1.0 - satStep * y;
ctx.fillStyle = Colors.hsvToHexString(hue, sat, 1.0);
ctx.fillRect(x, y, 1, 1);
}
}
const cx = Math.round(hue / hueStep);
const cy = Math.round((1.0 - sat) / satStep);
ctx.beginPath();
ctx.fillStyle = 'rgba(0, 0, 0, 1.0)';
ctx.lineWidth = 2;
ctx.moveTo(cx - PALETTE_CURSOR_HALFSIZE, cy);
ctx.lineTo(cx + PALETTE_CURSOR_HALFSIZE, cy);
ctx.moveTo(cx, cy - PALETTE_CURSOR_HALFSIZE);
ctx.lineTo(cx, cy + PALETTE_CURSOR_HALFSIZE);
ctx.closePath();
ctx.stroke();
this.lastPaletteX = cx;
this.lastPaletteY = cy;
}
private drawValueColumn() {
if (!this.valueColumnRef.current)
return;
const canvas = this.valueColumnRef.current;
const ctx = canvas.getContext('2d');
if (!ctx)
return;
this.drawValueColumnInternal(ctx, canvas.width, canvas.height);
this.drawValueColumnCursorInternal(ctx, canvas.width, canvas.height, this.state.v);
}
private drawValueColumnInternal(ctx: CanvasRenderingContext2D, width: number, height: number) {
const valStep = 1.0 / height;
for (let y = 0; y < height; y++) {
const cv = 1.0 - y * valStep;
ctx.fillStyle = Colors.hsvToHexString(this.state.h, this.state.s, cv);
ctx.fillRect(0, y, width, 1);
}
}
private drawValueColumnCursor(val: number) {
if (!this.valueColumnRef.current)
return;
const canvas = this.valueColumnRef.current;
const ctx = canvas.getContext('2d');
if (!ctx)
return;
this.drawValueColumnCursorInternal(ctx, canvas.width, canvas.height, val);
}
private drawValueColumnCursorInternal(ctx: CanvasRenderingContext2D, width: number, height: number, val: number) {
const valStep = 1.0 / height;
let fy = Math.floor(this.lastValueY - 1);
if (fy < 0) fy = 0;
let ty = Math.floor(fy + VALUE_CURSOR_THICKNESS + 2);
if (ty > height)
ty = height;
for (let y = fy; y < ty; y++) {
const cv = 1.0 - y * valStep;
ctx.fillStyle = Colors.hsvToHexString(this.state.h, this.state.s, cv);
ctx.fillRect(0, y, width, 1);
}
const y = Math.round((1.0 - val) / valStep);
const halfWidth = Math.round(width / 2);
ctx.fillStyle = 'rgba(255, 255, 255, 1.0)';
ctx.fillRect(0, y, halfWidth, VALUE_CURSOR_THICKNESS);
ctx.fillStyle = 'rgba(0, 0, 0, 1.0)';
ctx.fillRect(halfWidth, y, width - halfWidth, VALUE_CURSOR_THICKNESS);
this.lastValueY = y;
}
private paletteCoordsToHueSat(x: number, y: number) {
const palette = this.paletteRef.current!;
const h = 360 * x / palette.width;
const s = 1.0 - 1.0 * y / palette.height;
return { h, s };
}
private onGlobalMouseMovedValue = (evt: MouseEvent) => {
if ((evt.buttons & 1) === 0) {
window.removeEventListener('mousemove', this.onGlobalMouseMovedValue);
this.mouseListenerAttached = false;
return;
}
this.changeColorFromValue(evt.pageY);
};
private onGlobalMouseMovedPalette = (evt: MouseEvent) => {
if ((evt.buttons & 1) === 0) {
window.removeEventListener('mousemove', this.onGlobalMouseMovedPalette);
this.mouseListenerAttached = false;
return;
}
this.changeColorFromPalette(evt.pageX, evt.pageY);
};
private onGlobalTouchMovedPalette = (evt: TouchEvent) => {
if (evt.touches.length !== 0)
this.changeColorFromPalette(evt.touches[0].pageX, evt.touches[0].pageY);
};
private updateColorHsv(hsv: { h: number, s: number, v: number }) {
const rgb = Colors.hsv2rgb(hsv.h, hsv.s, hsv.v);
this.updateColor(rgb, hsv);
}
private updateColorRgb(rgb: { r: number, g: number, b: number }) {
const hsv = Colors.rgb2hsv(rgb.r, rgb.g, rgb.b);
this.updateColor(rgb, hsv);
}
private updateColor(rgb: { r: number, g: number, b: number }, hsv: { h: number, s: number, v: number }) {
let update: Partial<State> = { ...hsv };
if (this.state.rIn === '')
update = { ...update, rIn: rgb.r.toString() };
if (this.state.gIn === '')
update = { ...update, gIn: rgb.g.toString() };
if (this.state.bIn === '')
update = { ...update, bIn: rgb.b.toString() };
if (this.state.hIn === '')
update = { ...update, hIn: hsv.h.toString() };
if (this.state.sIn === '')
update = { ...update, sIn: hsv.s.toString() };
if (this.state.vIn === '')
update = { ...update, vIn: hsv.v.toString() };
this.setState({
...this.state,
...update,
});
}
private onGlobalTouchMovedValue = (evt: TouchEvent) => {
if (evt.touches.length !== 0)
this.changeColorFromValue(evt.touches[0].pageY);
};
private valueColumnCoordToVal(y: number) {
const valCol = this.valueColumnRef.current!;
return 1.0 - 1.0 * y / valCol.height;
}
componentDidMount() {
this.drawPalette();
this.drawValueColumn();
this.forceUpdate();
}
componentDidUpdate(_prevProps: ColorPicker.Props, prevState: State) {
if (this.state.h !== prevState.h || this.state.s !== prevState.s) {
this.drawPaletteCursor(this.state.h, this.state.s);
this.drawValueColumn();
}
if (this.state.v !== prevState.v) {
this.drawValueColumnCursor(this.state.v);
}
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.onGlobalMouseMovedValue);
window.removeEventListener('mousemove', this.onGlobalMouseMovedPalette);
window.removeEventListener('touchmove', this.onGlobalTouchMovedValue);
window.removeEventListener('touchmove', this.onGlobalTouchMovedPalette);
}
render() {
return (
<div
ref={this.selfRef}
style={{
background: 'white',
border: '0.15em solid #ccc',
boxShadow: '0 0 0.3em 0 rgba(0, 0, 0, 0.5)',
left: this.calcLeft(),
padding: '0.5em',
position: 'absolute',
top: this.calcTop(),
zIndex: 99,
}}
>
<div
style={{
display: 'grid',
gridColumnGap: '0.5em',
gridTemplateColumns: 'auto auto',
marginBottom: '0.5em',
}}
>
<canvas
width={360}
height={256}
ref={this.paletteRef}
onMouseDown={evt => {
if ((evt.buttons & 1) === 0 || this.mouseListenerAttached)
return;
this.changeColorFromPalette(evt.pageX, evt.pageY);
this.mouseListenerAttached = true;
window.addEventListener('mousemove', this.onGlobalMouseMovedPalette);
}}
onMouseUp={evt => {
if (evt.buttons & 1) {
window.removeEventListener('mousemove', this.onGlobalMouseMovedPalette);
this.mouseListenerAttached = false;
}
}}
onTouchStart={evt => {
if (this.touchListenerAttached)
return;
window.addEventListener('touchmove', this.onGlobalTouchMovedPalette);
this.touchListenerAttached = true;
if (evt.touches.length !== 0)
this.changeColorFromPalette(evt.touches[0].pageX, evt.touches[0].pageY);
}}
onTouchEnd={_evt => {
window.removeEventListener('touchmove', this.onGlobalTouchMovedPalette);
this.touchListenerAttached = false;
}}
/>
<canvas
width={30}
height={256}
ref={this.valueColumnRef}
style={{
height: '100%',
width: '1em',
}}
onMouseDown={evt => {
if ((evt.buttons & 1) === 0 || this.mouseListenerAttached)
return;
this.changeColorFromValue(evt.pageY);
this.mouseListenerAttached = true;
window.addEventListener('mousemove', this.onGlobalMouseMovedValue);
}}
onMouseUp={evt => {
if (evt.buttons & 1) {
window.removeEventListener('mousemove', this.onGlobalMouseMovedValue);
this.mouseListenerAttached = false;
}
}}
onTouchStart={evt => {
if (this.touchListenerAttached)
return;
window.addEventListener('touchmove', this.onGlobalTouchMovedValue);
this.touchListenerAttached = true;
if (evt.touches.length !== 0)
this.changeColorFromValue(evt.touches[0].pageY);
}}
onTouchEnd={_evt => {
window.removeEventListener('touchmove', this.onGlobalTouchMovedValue);
this.touchListenerAttached = false;
}}
onWheel={evt => {
if (evt.deltaY === 0)
return;
let v = this.state.v - 0.01 * Math.sign(evt.deltaY);
if (v < 0)
v = 0;
else if (v > 1)
v = 1;
this.setState({ ...this.state, v });
}}
/>
</div>
<div
style={{
display: 'flex',
marginBottom: '0.5em',
}}
>
<div
style={{
background: Colors.colorToHexString(this.props.initialColor),
flex: 1,
height: '2em',
}}
/>
<div
style={{
background: Colors.colorToHexString(Colors.colorFromHsv(this.state.h, this.state.s, this.state.v)),
flex: 1,
height: '2em',
}}
/>
</div>
<div
style={{
display: 'grid',
gridColumnGap: '0.5em',
gridTemplateColumns: 'auto 4em auto 4em auto 4em',
marginBottom: '0.5em',
}}
>
<div>R</div>
<SpinBox
min={MIN_RGB}
max={MAX_RGB}
step={1}
value={this.state.rIn === '' ? null : Math.round(Colors.hsv2rgb(this.state.h, this.state.s, this.state.v).r)}
onChange={rIn => {
if (rIn === '')
this.setState({ ...this.state, rIn });
else {
const r = stoi(rIn);
if (!isRgbVal(r))
return;
const { g, b } = Colors.hsv2rgb(this.state.h, this.state.s, this.state.v);
this.updateColorRgb({ r, g, b });
}
}}
pathPrefix={this.props.pathPrefix}
/>
<div>G</div>
<SpinBox
min={MIN_RGB}
max={MAX_RGB}
step={1}
value={this.state.gIn === '' ? null : Math.round(Colors.hsv2rgb(this.state.h, this.state.s, this.state.v).g)}
onChange={gIn => {
if (gIn === '')
this.setState({ ...this.state, gIn });
else {
const g = stoi(gIn);
if (!isRgbVal(g))
return;
const { r, b } = Colors.hsv2rgb(this.state.h, this.state.s, this.state.v);
this.updateColorRgb({ r, g, b });
}
}}
pathPrefix={this.props.pathPrefix}
/>
<div>B</div>
<SpinBox
min={MIN_RGB}
max={MAX_RGB}
step={1}
value={this.state.bIn === '' ? null : Math.round(Colors.hsv2rgb(this.state.h, this.state.s, this.state.v).b)}
onChange={bIn => {
if (bIn === '')
this.setState({ ...this.state, bIn });
else {
const b = stoi(bIn);
if (!isRgbVal(b))
return;
const { r, g } = Colors.hsv2rgb(this.state.h, this.state.s, this.state.v);
this.updateColorRgb({ r, g, b });
}
}}
pathPrefix={this.props.pathPrefix}
/>
</div>
<div
style={{
display: 'grid',
gridColumnGap: '0.5em',
gridTemplateColumns: 'auto 4em auto 4em auto 4em',
marginBottom: '0.5em',
}}
>
<div>H</div>
<SpinBox
min={MIN_HUE}
max={MAX_HUE}
step={1}
value={this.state.hIn === '' ? null : Math.round(this.state.h)}
onChange={hIn => {
if (hIn === '')
this.setState({ ...this.state, hIn });
else {
const h = stoi(hIn);
if (!isHueVal(h))
return;
this.updateColorHsv({ h, s: this.state.s, v: this.state.v });
}
}}
pathPrefix={this.props.pathPrefix}
/>
<div>S</div>
<SpinBox
min={MIN_SATVAL}
max={MAX_SATVAL}
step={1}
value={this.state.sIn === '' ? null : Math.round(this.state.s * 100)}
onChange={sIn => {
if (sIn === '')
this.setState({ ...this.state, sIn });
else {
const s = stoi(sIn);
if (!isSatValVal(s))
return;
this.updateColorHsv({ h: this.state.h, s: s / 100, v: this.state.v });
}
}}
pathPrefix={this.props.pathPrefix}
/>
<div>V</div>
<SpinBox
min={MIN_SATVAL}
max={MAX_SATVAL}
step={1}
value={this.state.vIn === '' ? null : Math.round(this.state.v * 100)}
onChange={vIn => {
if (vIn === '')
this.setState({ ...this.state, vIn });
else {
const v = stoi(vIn);
if (!isSatValVal(v))
return;
this.updateColorHsv({ h: this.state.h, s: this.state.s, v: v / 100 });
}
}}
pathPrefix={this.props.pathPrefix}
/>
</div>
<div
style={{
display: 'flex',
gap: '0.5em',
}}
>
<PushButton
text='OK'
onClick={() => {
this.props.onColorPicked(Colors.colorFromHsv(this.state.h, this.state.s, this.state.v));
this.dispose();
}}
enabled={true}
/>
<PushButton
text='Preview'
onClick={() => {
this.props.onColorPicked(Colors.colorFromHsv(this.state.h, this.state.s, this.state.v));
this.setState({ ...this.state, restoreOnCancel: true });
}}
enabled={true}
/>
<PushButton
text='Cancel'
onClick={() => {
if (this.state.restoreOnCancel)
this.props.onColorPicked(this.props.initialColor);
this.dispose();
}}
enabled={true}
/>
</div>
</div>
);
}
}
export namespace ColorPicker {
export interface OnColorPicked {
(color: number): void;
}
export interface Props {
initialColor: number;
left: number;
top: number;
onColorPicked: OnColorPicked;
parentElement: HTMLElement;
pathPrefix: string;
}
export function create<T>(evt: React.MouseEvent<T, MouseEvent>, initialColor: number, handler: OnColorPicked, pathPrefix = '') {
const tainer = document.createElement('div');
tainer.classList.add('rmsp-color-picker-nest');
document.body.appendChild(tainer);
ReactDOM.render(
<ColorPicker
initialColor={initialColor}
left={evt.clientX}
top={evt.clientY}
onColorPicked={handler}
parentElement={tainer}
pathPrefix={pathPrefix}
/>,
tainer
);
}
}
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Lada Biedermannová <Lada.Biedermannova@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Bohdan Schneider <Bohdan.Schneider@ibt.cas.cz>
*/
import { Color } from '../../mol-util/color';
function ntxs(num: number) {
num = Math.round(num);
const str = num.toString(16);
return num < 16 ? '0' + str : str;
}
export namespace Colors {
export function colorFromRgb(r: number, g: number, b: number) {
return Color.fromRgb(r, g, b);
}
export function colorFromHsv(h: number, s: number, v: number) {
const { r, g, b } = hsv2rgb(h, s, v);
return Color.fromRgb(r, g, b);
}
export function colorToHexString(clr: number) {
const [r, g, b] = Color.toRgb(Color(clr));
return rgbToHexString(r, g, b);
}
export function colorToHsv(clr: number) {
const [r, g, b] = Color.toRgb(Color(clr));
return rgb2hsv(r, g, b);
}
export function colorToRgb(clr: number) {
const [r, g, b] = Color.toRgb(Color(clr));
return { r, g, b };
}
export function hsv2rgb(h: number, s: number, v: number): { r: number, g: number, b: number } {
const f = (n: number, k = (n + h / 60) % 6) => v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);
return { r: f(5) * 255, g: f(3) * 255, b: f(1) * 255 };
}
export function hsvToColor(h: number, s: number, v: number) {
const { r, g, b } = hsv2rgb(h, s, v);
return Color.fromRgb(r, g, b);
}
export function hsvToHexString(h: number, s: number, v: number) {
const { r, g, b } = hsv2rgb(h, s, v);
return rgbToHexString(r, g, b);
}
export function rgbToHexString(r: number, g: number, b: number) {
return `#${ntxs(r)}${ntxs(g)}${ntxs(b)}`;
}
export function rgb2hsv(r: number, g: number, b: number) {
const rabs = r / 255;
const gabs = g / 255;
const babs = b / 255;
const v = Math.max(rabs, gabs, babs);
const diff = v - Math.min(rabs, gabs, babs);
const diffc = (c: number) => (v - c) / 6 / diff + 1 / 2;
let h = 0;
let s = 0;
if (diff !== 0) {
s = diff / v;
const rr = diffc(rabs);
const gg = diffc(gabs);
const bb = diffc(babs);
if (rabs === v) {
h = bb - gg;
} else if (gabs === v) {
h = (1 / 3) + rr - bb;
} else if (babs === v) {
h = (2 / 3) + gg - rr;
}
if (h < 0) {
h += 1;
} else if (h > 1) {
h -= 1;
}
}
return { h: h * 360, s, v };
}
}
export namespace NtCColors {
export const Classes = {
A: Color(0xFFC1C1),
B: Color(0xC8CFFF),
BII: Color(0x0059DA),
miB: Color(0x3BE8FB),
Z: Color(0x01F60E),
IC: Color(0xFA5CFB),
OPN: Color(0xE90000),
SYN: Color(0xFFFF01),
N: Color(0xF2F2F2),
};
export type Classes = typeof Classes;
export const Conformers = {
NANT_Upr: Classes.N,
NANT_Lwr: Classes.N,
AA00_Upr: Classes.A,
AA00_Lwr: Classes.A,
AA02_Upr: Classes.A,
AA02_Lwr: Classes.A,
AA03_Upr: Classes.A,
AA03_Lwr: Classes.A,
AA04_Upr: Classes.A,
AA04_Lwr: Classes.A,
AA08_Upr: Classes.A,
AA08_Lwr: Classes.A,
AA09_Upr: Classes.A,
AA09_Lwr: Classes.A,
AA01_Upr: Classes.A,
AA01_Lwr: Classes.A,
AA05_Upr: Classes.A,
AA05_Lwr: Classes.A,
AA06_Upr: Classes.A,
AA06_Lwr: Classes.A,
AA10_Upr: Classes.A,
AA10_Lwr: Classes.A,
AA11_Upr: Classes.A,
AA11_Lwr: Classes.A,
AA07_Upr: Classes.A,
AA07_Lwr: Classes.A,
AA12_Upr: Classes.A,
AA12_Lwr: Classes.A,
AA13_Upr: Classes.A,
AA13_Lwr: Classes.A,
AB01_Upr: Classes.A,
AB01_Lwr: Classes.B,
AB02_Upr: Classes.A,
AB02_Lwr: Classes.B,
AB03_Upr: Classes.A,
AB03_Lwr: Classes.B,
AB04_Upr: Classes.A,
AB04_Lwr: Classes.B,
AB05_Upr: Classes.A,
AB05_Lwr: Classes.B,
BA01_Upr: Classes.B,
BA01_Lwr: Classes.A,
BA05_Upr: Classes.B,
BA05_Lwr: Classes.A,
BA09_Upr: Classes.B,
BA09_Lwr: Classes.A,
BA08_Upr: Classes.BII,
BA08_Lwr: Classes.A,
BA10_Upr: Classes.B,
BA10_Lwr: Classes.A,
BA13_Upr: Classes.BII,
BA13_Lwr: Classes.A,
BA16_Upr: Classes.BII,
BA16_Lwr: Classes.A,
BA17_Upr: Classes.BII,
BA17_Lwr: Classes.A,
BB00_Upr: Classes.B,
BB00_Lwr: Classes.B,
BB01_Upr: Classes.B,
BB01_Lwr: Classes.B,
BB17_Upr: Classes.B,
BB17_Lwr: Classes.B,
BB02_Upr: Classes.B,
BB02_Lwr: Classes.B,
BB03_Upr: Classes.B,
BB03_Lwr: Classes.B,
BB11_Upr: Classes.B,
BB11_Lwr: Classes.B,
BB16_Upr: Classes.B,
BB16_Lwr: Classes.B,
BB04_Upr: Classes.B,
BB04_Lwr: Classes.BII,
BB05_Upr: Classes.B,
BB05_Lwr: Classes.BII,
BB07_Upr: Classes.BII,
BB07_Lwr: Classes.BII,
BB08_Upr: Classes.BII,
BB08_Lwr: Classes.BII,
BB10_Upr: Classes.miB,
BB10_Lwr: Classes.miB,
BB12_Upr: Classes.miB,
BB12_Lwr: Classes.miB,
BB13_Upr: Classes.miB,
BB13_Lwr: Classes.miB,
BB14_Upr: Classes.miB,
BB14_Lwr: Classes.miB,
BB15_Upr: Classes.miB,
BB15_Lwr: Classes.miB,
BB20_Upr: Classes.miB,
BB20_Lwr: Classes.miB,
IC01_Upr: Classes.IC,
IC01_Lwr: Classes.IC,
IC02_Upr: Classes.IC,
IC02_Lwr: Classes.IC,
IC03_Upr: Classes.IC,
IC03_Lwr: Classes.IC,
IC04_Upr: Classes.IC,
IC04_Lwr: Classes.IC,
IC05_Upr: Classes.IC,
IC05_Lwr: Classes.IC,
IC06_Upr: Classes.IC,
IC06_Lwr: Classes.IC,
IC07_Upr: Classes.IC,
IC07_Lwr: Classes.IC,
OP01_Upr: Classes.OPN,
OP01_Lwr: Classes.OPN,
OP02_Upr: Classes.OPN,
OP02_Lwr: Classes.OPN,
OP03_Upr: Classes.OPN,
OP03_Lwr: Classes.OPN,
OP04_Upr: Classes.OPN,
OP04_Lwr: Classes.OPN,
OP05_Upr: Classes.OPN,
OP05_Lwr: Classes.OPN,
OP06_Upr: Classes.OPN,
OP06_Lwr: Classes.OPN,
OP07_Upr: Classes.OPN,
OP07_Lwr: Classes.OPN,
OP08_Upr: Classes.OPN,
OP08_Lwr: Classes.OPN,
OP09_Upr: Classes.OPN,
OP09_Lwr: Classes.OPN,
OP10_Upr: Classes.OPN,
OP10_Lwr: Classes.OPN,
OP11_Upr: Classes.OPN,
OP11_Lwr: Classes.OPN,
OP12_Upr: Classes.OPN,
OP12_Lwr: Classes.OPN,
OP13_Upr: Classes.OPN,
OP13_Lwr: Classes.OPN,
OP14_Upr: Classes.OPN,
OP14_Lwr: Classes.OPN,
OP15_Upr: Classes.OPN,
OP15_Lwr: Classes.OPN,
OP16_Upr: Classes.OPN,
OP16_Lwr: Classes.OPN,
OP17_Upr: Classes.OPN,
OP17_Lwr: Classes.OPN,
OP18_Upr: Classes.OPN,
OP18_Lwr: Classes.OPN,
OP19_Upr: Classes.OPN,
OP19_Lwr: Classes.OPN,
OP20_Upr: Classes.OPN,
OP20_Lwr: Classes.OPN,
OP21_Upr: Classes.OPN,
OP21_Lwr: Classes.OPN,
OP22_Upr: Classes.OPN,
OP22_Lwr: Classes.OPN,
OP23_Upr: Classes.OPN,
OP23_Lwr: Classes.OPN,
OP24_Upr: Classes.OPN,
OP24_Lwr: Classes.OPN,
OP25_Upr: Classes.OPN,
OP25_Lwr: Classes.OPN,
OP26_Upr: Classes.OPN,
OP26_Lwr: Classes.OPN,
OP27_Upr: Classes.OPN,
OP27_Lwr: Classes.OPN,
OP28_Upr: Classes.OPN,
OP28_Lwr: Classes.OPN,
OP29_Upr: Classes.OPN,
OP29_Lwr: Classes.OPN,
OP30_Upr: Classes.OPN,
OP30_Lwr: Classes.OPN,
OP31_Upr: Classes.OPN,
OP31_Lwr: Classes.OPN,
OPS1_Upr: Classes.OPN,
OPS1_Lwr: Classes.OPN,
OP1S_Upr: Classes.OPN,
OP1S_Lwr: Classes.OPN,
AAS1_Upr: Classes.SYN,
AAS1_Lwr: Classes.A,
AB1S_Upr: Classes.A,
AB1S_Lwr: Classes.SYN,
AB2S_Upr: Classes.A,
AB2S_Lwr: Classes.SYN,
BB1S_Upr: Classes.B,
BB1S_Lwr: Classes.SYN,
BB2S_Upr: Classes.B,
BB2S_Lwr: Classes.SYN,
BBS1_Upr: Classes.SYN,
BBS1_Lwr: Classes.B,
ZZ01_Upr: Classes.Z,
ZZ01_Lwr: Classes.Z,
ZZ02_Upr: Classes.Z,
ZZ02_Lwr: Classes.Z,
ZZ1S_Upr: Classes.Z,
ZZ1S_Lwr: Classes.SYN,
ZZ2S_Upr: Classes.Z,
ZZ2S_Lwr: Classes.SYN,
ZZS1_Upr: Classes.SYN,
ZZS1_Lwr: Classes.Z,
ZZS2_Upr: Classes.SYN,
ZZS2_Lwr: Classes.Z,
};
export type Conformers = typeof Conformers;
}
import React from 'react';
export class PushButton extends React.Component<{ text: string, enabled: boolean, onClick: () => void }> {
render() {
return (
<div
className={`rmsp-pushbutton ${this.props.enabled ? '' : 'rmsp-pushbutton-disabled'}`}
onClick={() => this.props.enabled ? this.props.onClick() : {}}
>
<div className={`${this.props.enabled ? 'rmsp-pushbutton-text' : 'rmsp-pushbutton-text-disabled'}`}>{this.props.text}</div>
</div>
);
}
}
export class ToggleButton extends React.Component<{ text: string, enabled: boolean, switchedOn: boolean, onClick: () => void }> {
render() {
return (
<div
className={`rmsp-pushbutton ${this.props.enabled ? (this.props.switchedOn ? 'rmsp-togglebutton-switched-on' : 'rmsp-togglebutton-switched-off') : 'rmsp-pushbutton-disabled'}`}
onClick={() => this.props.enabled ? this.props.onClick() : {}}
>
<div className={`${this.props.enabled ? 'rmsp-pushbutton-text' : 'rmsp-pushbutton-text-disabled'}`}>{this.props.text}</div>
</div>
);
}
}
export class SpinBox extends React.Component<SpinBox.Props> {
private clsDisabled() {
return this.props.classNameDisabled ?? 'rmsp-spinbox-input-disabled';
}
private clsEnabled() {
return this.props.className ?? 'rmsp-spinbox-input';
}
private decrease() {
if (this.props.value === null)
return;
const nv = this.props.value - this.props.step;
if (nv >= this.props.min)
this.props.onChange(nv.toString());
}
private increase() {
if (this.props.value === null)
return;
const nv = this.props.value + this.props.step;
if (nv >= this.props.min)
this.props.onChange(nv.toString());
}
render() {
return (
<div className='rmsp-spinbox-container'>
<input
type='text'
className={this.props.disabled ? this.clsDisabled() : this.clsEnabled()}
value={this.props.formatter ? this.props.formatter(this.props.value) : this.props.value?.toString() ?? ''}
onChange={evt => this.props.onChange(evt.currentTarget.value)}
onWheel={evt => {
evt.stopPropagation();
if (this.props.value === null)
return;
if (evt.deltaY < 0) {
const nv = this.props.value + this.props.step;
if (nv <= this.props.max)
this.props.onChange(nv.toString());
} else if (evt.deltaY > 0) {
const nv = this.props.value - this.props.step;
if (nv >= this.props.min)
this.props.onChange(nv.toString());
}
}}
/>
<div className='rmsp-spinbox-buttons'>
<img
className='rmsp-spinbox-button'
src={`./${this.props.pathPrefix}assets/imgs/triangle-up.svg`} onClick={() => this.increase()}
/>
<img
className='rmsp-spinbox-button'
src={`./${this.props.pathPrefix}assets/imgs/triangle-down.svg`} onClick={() => this.decrease()}
/>
</div>
</div>
);
}
}
export namespace SpinBox {
export interface Formatter {
(v: number|null): string;
}
export interface OnChange {
(newValue: string): void;
}
export interface Props {
value: number|null;
onChange: OnChange;
min: number;
max: number;
step: number;
pathPrefix: string;
disabled?: boolean;
className?: string;
classNameDisabled?: string;
formatter?: Formatter;
}
}
export type ID ='data'|'structure'|'visual'|'pyramids'; export type ID ='data'|'trajectory'|'model'|'structure'|'visual'|'pyramids';
export type Substructure = 'nucleic'|'protein'|'water'; export type Substructure = 'nucleic'|'protein'|'water';
export function ID(id: ID, sub: Substructure|'', ref: string) { export function ID(id: ID, sub: Substructure|'', ref: string) {
if (sub === '') if (sub === '')
return `${id}_${ref}`; return `${ref}_${id}`;
return `${id}_${sub}_${ref}`; return `${ref}_${sub}_${id}`;
}
export function isVisual(ident: string) {
return ident.endsWith('_visual');
} }
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="molstar.css" /> <link rel="stylesheet" type="text/css" href="molstar.css" />
</head> </head>
<body style="height: 100vh; width: 100hw"> <body style="height: 100vh; display: flex; overflow: hidden;">
<div id="xxx-app" style="height: 100%; width: 100%"></div> <div id="xxx-app" style="flex: 1"></div>
<script type="text/javascript" src="./molstar.js"></script> <script type="text/javascript" src="./molstar.js"></script>
<script> <script>
async function loadStructure() { async function loadStructure() {
......
This diff is collapsed.
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
.rmsp-app { .rmsp-app {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 90%; height: 100%;
width: 100%; width: 100%;
font-family: Verdana, sans-serif; font-family: Verdana, sans-serif;
...@@ -36,6 +36,20 @@ ...@@ -36,6 +36,20 @@
line-height: 1.5; line-height: 1.5;
} }
.rmsp-color-box {
display: flex;
}
.rmsp-color-box-color {
flex: 1;
}
.rmsp-color-picker-nest {
font-family: Verdana, sans-serif;
font-size: 12pt;
line-height: 1.5;
}
.rmsp-controls { .rmsp-controls {
display: grid; display: grid;
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
...@@ -43,6 +57,12 @@ ...@@ -43,6 +57,12 @@
overflow: scroll; overflow: scroll;
} }
.rmsp-control-item-group {
display: flex;
flex: 1;
gap: 0;
}
.rmsp-control-item { .rmsp-control-item {
flex: 1; flex: 1;
} }
...@@ -90,6 +110,41 @@ ...@@ -90,6 +110,41 @@
margin: 0.15em; margin: 0.15em;
} }
.rmsp-spinbox-container {
background-color: white;
border: 0.15em solid #ccc;
border-radius: 0;
display: grid;
grid-template-columns: auto 1fr;
}
.rmsp-spinbox-button {
height: 0.6em;
text-align: center;
}
.rmsp-spinbox-button:hover {
background-color: #aaa;
}
.rmsp-spinbox-buttons {
display: flex;
flex-direction: column;
gap: 0.05em;
margin: 0.1em;
}
.rmsp-spinbox-input {
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
border: none;
width: 100%;
}
.rmsp-spinbox-input:focus {
outline: none;
}
.rmsp-viewer { .rmsp-viewer {
flex: 1; flex: 1;
position: relative; position: relative;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment