Street testing fixes
This commit is contained in:
@@ -85,6 +85,7 @@ add_custom_command(
|
|||||||
src/WifiConfigDialog.tsx
|
src/WifiConfigDialog.tsx
|
||||||
src/NoChangePassword.tsx
|
src/NoChangePassword.tsx
|
||||||
src/XxxSnippet.tsx
|
src/XxxSnippet.tsx
|
||||||
|
src/ZoomedUiControl.tsx
|
||||||
|
|
||||||
public/css/roboto.css
|
public/css/roboto.css
|
||||||
public/fonts/Roboto-BlackItalic.woff2
|
public/fonts/Roboto-BlackItalic.woff2
|
||||||
|
|||||||
+33
-11
@@ -48,7 +48,8 @@ import SettingsDialog from './SettingsDialog';
|
|||||||
import AboutDialog from './AboutDialog';
|
import AboutDialog from './AboutDialog';
|
||||||
import BankDialog from './BankDialog';
|
import BankDialog from './BankDialog';
|
||||||
|
|
||||||
import { PiPedalModelFactory, PiPedalModel, State } from './PiPedalModel';
|
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo } from './PiPedalModel';
|
||||||
|
import ZoomedUiControl from './ZoomedUiControl'
|
||||||
import MainPage from './MainPage';
|
import MainPage from './MainPage';
|
||||||
import DialogContent from '@material-ui/core/DialogContent';
|
import DialogContent from '@material-ui/core/DialogContent';
|
||||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||||
@@ -59,6 +60,7 @@ import { BankIndex, BankIndexEntry } from './Banks';
|
|||||||
import RenameDialog from './RenameDialog';
|
import RenameDialog from './RenameDialog';
|
||||||
import JackStatusView from './JackStatusView';
|
import JackStatusView from './JackStatusView';
|
||||||
|
|
||||||
|
|
||||||
const theme = createMuiTheme({
|
const theme = createMuiTheme({
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
@@ -86,7 +88,7 @@ const appStyles = ({ palette, spacing, mixins }: Theme) => createStyles({
|
|||||||
zIndex: 2010
|
zIndex: 2010
|
||||||
},
|
},
|
||||||
errorContent: {
|
errorContent: {
|
||||||
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
@@ -270,6 +272,8 @@ function preventDefault(e: SyntheticEvent): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AppState = {
|
type AppState = {
|
||||||
|
zoomedControlInfo: ZoomedControlInfo | undefined;
|
||||||
|
|
||||||
isDrawerOpen: boolean;
|
isDrawerOpen: boolean;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
displayState: State;
|
displayState: State;
|
||||||
@@ -288,6 +292,7 @@ type AppState = {
|
|||||||
aboutDialogOpen: boolean;
|
aboutDialogOpen: boolean;
|
||||||
bankDialogOpen: boolean;
|
bankDialogOpen: boolean;
|
||||||
editBankDialogOpen: boolean;
|
editBankDialogOpen: boolean;
|
||||||
|
zoomedControlOpen: boolean;
|
||||||
|
|
||||||
|
|
||||||
presetName: string;
|
presetName: string;
|
||||||
@@ -309,7 +314,17 @@ const App = withStyles(appStyles)(class extends ResizeResponsiveComponent<AppPro
|
|||||||
super(props);
|
super(props);
|
||||||
this.model_ = PiPedalModelFactory.getInstance();
|
this.model_ = PiPedalModelFactory.getInstance();
|
||||||
|
|
||||||
|
this.model_.zoomedUiControl.addOnChangedHandler(
|
||||||
|
()=> {
|
||||||
|
this.setState({
|
||||||
|
zoomedControlOpen: this.model_.zoomedUiControl.get() !== undefined,
|
||||||
|
zoomedControlInfo: this.model_.zoomedUiControl.get()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
zoomedControlInfo: this.model_.zoomedUiControl.get(),
|
||||||
isDrawerOpen: false,
|
isDrawerOpen: false,
|
||||||
errorMessage: this.model_.errorMessage.get(),
|
errorMessage: this.model_.errorMessage.get(),
|
||||||
displayState: this.model_.state.get(),
|
displayState: this.model_.state.get(),
|
||||||
@@ -328,6 +343,7 @@ const App = withStyles(appStyles)(class extends ResizeResponsiveComponent<AppPro
|
|||||||
aboutDialogOpen: false,
|
aboutDialogOpen: false,
|
||||||
bankDialogOpen: false,
|
bankDialogOpen: false,
|
||||||
editBankDialogOpen: false,
|
editBankDialogOpen: false,
|
||||||
|
zoomedControlOpen: false,
|
||||||
bankDisplayItems: 5
|
bankDisplayItems: 5
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -341,6 +357,7 @@ const App = withStyles(appStyles)(class extends ResizeResponsiveComponent<AppPro
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onOpenBank(bankId: number) {
|
onOpenBank(bankId: number) {
|
||||||
this.model_.openBank(bankId)
|
this.model_.openBank(bankId)
|
||||||
.catch((error) => this.model_.showAlert(error));
|
.catch((error) => this.model_.showAlert(error));
|
||||||
@@ -475,8 +492,7 @@ const App = withStyles(appStyles)(class extends ResizeResponsiveComponent<AppPro
|
|||||||
|
|
||||||
window.addEventListener("beforeunload", (e) => {
|
window.addEventListener("beforeunload", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this.model_.state.get() === State.Ready)
|
if (this.model_.state.get() === State.Ready) {
|
||||||
{
|
|
||||||
e.returnValue = "Are you sure you want to leave this page?";
|
e.returnValue = "Are you sure you want to leave this page?";
|
||||||
return "Are you sure you want to leave this page?";
|
return "Are you sure you want to leave this page?";
|
||||||
}
|
}
|
||||||
@@ -539,7 +555,7 @@ const App = withStyles(appStyles)(class extends ResizeResponsiveComponent<AppPro
|
|||||||
// ENTRY_HEIGHT*6 +K = 727 from observation.
|
// ENTRY_HEIGHT*6 +K = 727 from observation.
|
||||||
const K = 450;
|
const K = 450;
|
||||||
|
|
||||||
let bankEntries = Math.floor((height-K)/ENTRY_HEIGHT);
|
let bankEntries = Math.floor((height - K) / ENTRY_HEIGHT);
|
||||||
if (bankEntries < 1) bankEntries = 1;
|
if (bankEntries < 1) bankEntries = 1;
|
||||||
if (bankEntries > 7) bankEntries = 7;
|
if (bankEntries > 7) bankEntries = 7;
|
||||||
this.setState({ bankDisplayItems: bankEntries });
|
this.setState({ bankDisplayItems: bankEntries });
|
||||||
@@ -609,12 +625,11 @@ const App = withStyles(appStyles)(class extends ResizeResponsiveComponent<AppPro
|
|||||||
display: "flex", flexDirection: "column", flexWrap: "nowrap",
|
display: "flex", flexDirection: "column", flexWrap: "nowrap",
|
||||||
overscrollBehavior: this.state.isDebug ? "auto" : "none"
|
overscrollBehavior: this.state.isDebug ? "auto" : "none"
|
||||||
}}
|
}}
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => {
|
||||||
if (!this.model_.serverVersion?.debug??false)
|
if (!this.model_.serverVersion?.debug ?? false) {
|
||||||
{
|
e.preventDefault(); e.stopPropagation();
|
||||||
e.preventDefault(); e.stopPropagation();
|
}
|
||||||
}
|
}}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
{(!this.state.tinyToolBar) ?
|
{(!this.state.tinyToolBar) ?
|
||||||
@@ -769,6 +784,13 @@ const App = withStyles(appStyles)(class extends ResizeResponsiveComponent<AppPro
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ZoomedUiControl
|
||||||
|
dialogOpen={this.state.zoomedControlOpen}
|
||||||
|
controlInfo={this.state.zoomedControlInfo}
|
||||||
|
onDialogClose={()=>{ this.setState({zoomedControlOpen: false});} }
|
||||||
|
onDialogClosed={()=>{ this.model_.zoomedUiControl.set(undefined); }
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Dialog
|
<Dialog
|
||||||
open={this.state.alertDialogOpen}
|
open={this.state.alertDialogOpen}
|
||||||
onClose={this.handleCloseAlert}
|
onClose={this.handleCloseAlert}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
|
|||||||
uploadAfter = await this.model.uploadBank(fileList[i], uploadAfter);
|
uploadAfter = await this.model.uploadBank(fileList[i], uploadAfter);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.model.showAlert(error);
|
this.model.showAlert(error +"");
|
||||||
};
|
};
|
||||||
return uploadAfter;
|
return uploadAfter;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ const FullScreenIME =
|
|||||||
<Typography variant="body2">{this.props.caption}</Typography>
|
<Typography variant="body2">{this.props.caption}</Typography>
|
||||||
<Input key={value}
|
<Input key={value}
|
||||||
type="number"
|
type="number"
|
||||||
defaultValue={control.formatValue(value)}
|
defaultValue={control.formatShortValue(value)}
|
||||||
error={this.state.error}
|
error={this.state.error}
|
||||||
autoFocus
|
autoFocus
|
||||||
inputProps={{
|
inputProps={{
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import { createStyles, Theme, withStyles, WithStyles } from '@material-ui/core/styles';
|
import { createStyles, Theme, withStyles, WithStyles } from '@material-ui/core/styles';
|
||||||
import { MonitorPortHandle, PiPedalModel, PiPedalModelFactory } from "./PiPedalModel";
|
import { MonitorPortHandle, PiPedalModel, State, PiPedalModelFactory } from "./PiPedalModel";
|
||||||
import SvgPathBuilder from './SvgPathBuilder'
|
import SvgPathBuilder from './SvgPathBuilder'
|
||||||
|
|
||||||
//const char* model[] = {"12-TET","19-TET","24-TET", "31-TET", "53-TET"};
|
//const char* model[] = {"12-TET","19-TET","24-TET", "31-TET", "53-TET"};
|
||||||
@@ -120,6 +120,7 @@ const GxTunerControl =
|
|||||||
|
|
||||||
};
|
};
|
||||||
this.onFrequencyUpdated = this.onFrequencyUpdated.bind(this);
|
this.onFrequencyUpdated = this.onFrequencyUpdated.bind(this);
|
||||||
|
this.onStateChanged = this.onStateChanged.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -225,8 +226,19 @@ const GxTunerControl =
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onStateChanged(state: State)
|
||||||
|
{
|
||||||
|
// initial or reconnect
|
||||||
|
if (state === State.Ready)
|
||||||
|
{
|
||||||
|
this.removeSubscription();
|
||||||
|
this.updateSubscription();
|
||||||
|
}
|
||||||
|
}
|
||||||
componentDidMount()
|
componentDidMount()
|
||||||
{
|
{
|
||||||
|
this.model.state.addOnChangedHandler(this.onStateChanged);
|
||||||
this.addSubscription();
|
this.addSubscription();
|
||||||
}
|
}
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
@@ -234,6 +246,7 @@ const GxTunerControl =
|
|||||||
}
|
}
|
||||||
componentWillUnmount()
|
componentWillUnmount()
|
||||||
{
|
{
|
||||||
|
this.model.state.removeOnChangedHandler(this.onStateChanged);
|
||||||
this.removeSubscription();
|
this.removeSubscription();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+97
-8
@@ -189,6 +189,12 @@ export enum PluginType {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ControlType {
|
||||||
|
Dial,
|
||||||
|
OnOffSwitch,
|
||||||
|
Toggle,
|
||||||
|
Select
|
||||||
|
}
|
||||||
|
|
||||||
export class UiControl implements Deserializable<UiControl> {
|
export class UiControl implements Deserializable<UiControl> {
|
||||||
deserialize(input: any): UiControl
|
deserialize(input: any): UiControl
|
||||||
@@ -210,9 +216,31 @@ export class UiControl implements Deserializable<UiControl> {
|
|||||||
this.scale_points = ScalePoint.deserialize_array(input.scale_points);
|
this.scale_points = ScalePoint.deserialize_array(input.scale_points);
|
||||||
this.port_group = input.port_group;
|
this.port_group = input.port_group;
|
||||||
this.units = input.units as Units;
|
this.units = input.units as Units;
|
||||||
|
|
||||||
|
this.controlType = ControlType.Dial;
|
||||||
|
if (this.enumeration_property && this.scale_points.length === 2)
|
||||||
|
{
|
||||||
|
this.controlType = ControlType.Toggle;
|
||||||
|
} else {
|
||||||
|
if (this.min_value === 0 && this.max_value === 1)
|
||||||
|
{
|
||||||
|
if (this.toggled_property || this.enumeration_property || this.integer_property)
|
||||||
|
{
|
||||||
|
this.controlType = ControlType.OnOffSwitch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.controlType === ControlType.Dial && this.enumeration_property)
|
||||||
|
{
|
||||||
|
this.controlType = ControlType.Select;
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controlType: ControlType = ControlType.Dial; // non-serializable.
|
||||||
|
|
||||||
static deserialize_array(input: any): UiControl[] {
|
static deserialize_array(input: any): UiControl[] {
|
||||||
let result: UiControl[] = [];
|
let result: UiControl[] = [];
|
||||||
for (let i = 0; i < input.length; ++i)
|
for (let i = 0; i < input.length; ++i)
|
||||||
@@ -240,18 +268,18 @@ export class UiControl implements Deserializable<UiControl> {
|
|||||||
units: Units = Units.none;
|
units: Units = Units.none;
|
||||||
|
|
||||||
isOnOffSwitch() : boolean {
|
isOnOffSwitch() : boolean {
|
||||||
if (this.isAbToggle()) return false;
|
return this.controlType === ControlType.OnOffSwitch;
|
||||||
if (this.min_value !== 0 || this.max_value !== 1) return false;
|
|
||||||
return (this.toggled_property || this.enumeration_property || this.integer_property);
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isAbToggle(): boolean {
|
isAbToggle(): boolean {
|
||||||
if (this.min_value !== 0 || this.max_value !== 1) return false;
|
return this.controlType === ControlType.Toggle;
|
||||||
return this.enumeration_property && this.scale_points.length === 2;
|
|
||||||
}
|
}
|
||||||
isSelect() : boolean {
|
isSelect() : boolean {
|
||||||
return this.enumeration_property && !this.isOnOffSwitch() && !this.isAbToggle();
|
return this.controlType === ControlType.Select;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDial() : boolean {
|
||||||
|
return this.controlType === ControlType.Dial;
|
||||||
}
|
}
|
||||||
|
|
||||||
valueToRange(value: number): number {
|
valueToRange(value: number): number {
|
||||||
@@ -289,10 +317,71 @@ export class UiControl implements Deserializable<UiControl> {
|
|||||||
return this.rangeToValue(this.valueToRange(value));
|
return this.rangeToValue(this.valueToRange(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
formatValue(value: number): string {
|
formatDisplayValue(value: number): string {
|
||||||
if (this.integer_property || this.enumeration_property) {
|
if (this.integer_property || this.enumeration_property) {
|
||||||
value = Math.round(value);
|
value = Math.round(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this.scale_points.length; ++i)
|
||||||
|
{
|
||||||
|
let scalePoint = this.scale_points[i];
|
||||||
|
if (scalePoint.value === value)
|
||||||
|
{
|
||||||
|
return scalePoint.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let text = this.formatShortValue(value);
|
||||||
|
|
||||||
|
switch (this.units) {
|
||||||
|
case Units.bpm:
|
||||||
|
text += "bpm";
|
||||||
|
break;
|
||||||
|
case Units.cent:
|
||||||
|
text += "cents";
|
||||||
|
break;
|
||||||
|
case Units.cm:
|
||||||
|
text += "cm";
|
||||||
|
break;
|
||||||
|
case Units.db:
|
||||||
|
text += "dB";
|
||||||
|
break;
|
||||||
|
case Units.hz:
|
||||||
|
text += "Hz";
|
||||||
|
break;
|
||||||
|
case Units.khz:
|
||||||
|
text += "kHz";
|
||||||
|
break;
|
||||||
|
case Units.km:
|
||||||
|
text += "km";
|
||||||
|
break;
|
||||||
|
case Units.m:
|
||||||
|
text += "m";
|
||||||
|
break;
|
||||||
|
case Units.mhz:
|
||||||
|
text += "MHz";
|
||||||
|
break;
|
||||||
|
case Units.min:
|
||||||
|
text += "min";
|
||||||
|
break;
|
||||||
|
case Units.ms:
|
||||||
|
text += "ms";
|
||||||
|
break;
|
||||||
|
case Units.pc:
|
||||||
|
text += "%";
|
||||||
|
break;
|
||||||
|
case Units.s:
|
||||||
|
text += "s";
|
||||||
|
break;
|
||||||
|
// Midinote: not handled.
|
||||||
|
// semitone12TET not handled.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
formatShortValue(value: number): string
|
||||||
|
{
|
||||||
if (this.enumeration_property) {
|
if (this.enumeration_property) {
|
||||||
for (let i = 0; i < this.scale_points.length; ++i) {
|
for (let i = 0; i < this.scale_points.length; ++i) {
|
||||||
let scale_point = this.scale_points[i];
|
let scale_point = this.scale_points[i];
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import SvgPathBuilder from './SvgPathBuilder';
|
|||||||
import Draggable from './Draggable'
|
import Draggable from './Draggable'
|
||||||
import Rect from './Rect';
|
import Rect from './Rect';
|
||||||
import {PiPedalStateError} from './PiPedalError';
|
import {PiPedalStateError} from './PiPedalError';
|
||||||
|
import Utility from './Utility'
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -645,12 +646,6 @@ const PedalBoardView =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isTouchDevice(): boolean {
|
|
||||||
return (('ontouchstart' in window) ||
|
|
||||||
(navigator.maxTouchPoints > 0) ||
|
|
||||||
(navigator["msMaxTouchPoints"] > 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
onItemDoubleClick(event: SyntheticEvent, instanceId?: number): void {
|
onItemDoubleClick(event: SyntheticEvent, instanceId?: number): void {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -665,7 +660,7 @@ const PedalBoardView =
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
if (!this.isTouchDevice()) {
|
if (!Utility.isTouchDevice()) {
|
||||||
if (this.props.onDoubleClick && instanceId) {
|
if (this.props.onDoubleClick && instanceId) {
|
||||||
this.props.onDoubleClick(instanceId);
|
this.props.onDoubleClick(instanceId);
|
||||||
}
|
}
|
||||||
@@ -673,7 +668,7 @@ const PedalBoardView =
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX set keys on output objects !!
|
||||||
renderConnector(output: ReactNode[], item: PedalLayout, enabled: boolean): void {
|
renderConnector(output: ReactNode[], item: PedalLayout, enabled: boolean): void {
|
||||||
// let classes = this.props.classes;
|
// let classes = this.props.classes;
|
||||||
let x_ = item.bounds.x + CELL_WIDTH / 2;
|
let x_ = item.bounds.x + CELL_WIDTH / 2;
|
||||||
|
|||||||
@@ -41,9 +41,15 @@ export enum State {
|
|||||||
Loading,
|
Loading,
|
||||||
Ready,
|
Ready,
|
||||||
Error,
|
Error,
|
||||||
|
Background,
|
||||||
Reconnecting
|
Reconnecting
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ZoomedControlInfo {
|
||||||
|
source: HTMLElement;
|
||||||
|
instanceId: number;
|
||||||
|
uiControl: UiControl;
|
||||||
|
}
|
||||||
|
|
||||||
export interface VuUpdateInfo {
|
export interface VuUpdateInfo {
|
||||||
instanceId: number;
|
instanceId: number;
|
||||||
@@ -218,6 +224,10 @@ export class PresetIndex {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum VisibilityState {
|
||||||
|
Visible,
|
||||||
|
Hidden
|
||||||
|
};
|
||||||
interface MonitorPortOutputBody {
|
interface MonitorPortOutputBody {
|
||||||
subscriptionHandle: number;
|
subscriptionHandle: number;
|
||||||
value: number;
|
value: number;
|
||||||
@@ -261,7 +271,10 @@ export interface PiPedalModel {
|
|||||||
serverVersion?: PiPedalVersion;
|
serverVersion?: PiPedalVersion;
|
||||||
errorMessage: ObservableProperty<string>;
|
errorMessage: ObservableProperty<string>;
|
||||||
alertMessage: ObservableProperty<string>;
|
alertMessage: ObservableProperty<string>;
|
||||||
|
|
||||||
state: ObservableProperty<State>;
|
state: ObservableProperty<State>;
|
||||||
|
visibilityState: ObservableProperty<VisibilityState>;
|
||||||
|
|
||||||
ui_plugins: ObservableProperty<UiPlugin[]>;
|
ui_plugins: ObservableProperty<UiPlugin[]>;
|
||||||
plugin_classes: ObservableProperty<PluginClass>;
|
plugin_classes: ObservableProperty<PluginClass>;
|
||||||
jackConfiguration: ObservableProperty<JackConfiguration>;
|
jackConfiguration: ObservableProperty<JackConfiguration>;
|
||||||
@@ -272,6 +285,9 @@ export interface PiPedalModel {
|
|||||||
|
|
||||||
presets: ObservableProperty<PresetIndex>;
|
presets: ObservableProperty<PresetIndex>;
|
||||||
|
|
||||||
|
|
||||||
|
zoomedUiControl: ObservableProperty< ZoomedControlInfo| undefined >;
|
||||||
|
|
||||||
setJackServerSettings(jackServerSettings: JackServerSettings): void;
|
setJackServerSettings(jackServerSettings: JackServerSettings): void;
|
||||||
setMidiBinding(instanceId: number, midiBinding: MidiBinding): void;
|
setMidiBinding(instanceId: number, midiBinding: MidiBinding): void;
|
||||||
|
|
||||||
@@ -350,6 +366,9 @@ export interface PiPedalModel {
|
|||||||
getWifiChannels(countryIso3661: string): Promise<WifiChannel[]>;
|
getWifiChannels(countryIso3661: string): Promise<WifiChannel[]>;
|
||||||
|
|
||||||
getAlsaDevices() : Promise<AlsaDeviceInfo[]>;
|
getAlsaDevices() : Promise<AlsaDeviceInfo[]>;
|
||||||
|
|
||||||
|
zoomUiControl(sourceElement: HTMLElement,instanceId: number,uiControl: UiControl): void;
|
||||||
|
clearZoomedControl(): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PiPedalModelImpl implements PiPedalModel {
|
class PiPedalModelImpl implements PiPedalModel {
|
||||||
@@ -365,6 +384,8 @@ class PiPedalModelImpl implements PiPedalModel {
|
|||||||
ui_plugins: ObservableProperty<UiPlugin[]>
|
ui_plugins: ObservableProperty<UiPlugin[]>
|
||||||
= new ObservableProperty<UiPlugin[]>([]);
|
= new ObservableProperty<UiPlugin[]>([]);
|
||||||
state: ObservableProperty<State> = new ObservableProperty<State>(State.Loading);
|
state: ObservableProperty<State> = new ObservableProperty<State>(State.Loading);
|
||||||
|
visibilityState: ObservableProperty<VisibilityState> = new ObservableProperty<VisibilityState>(VisibilityState.Visible);
|
||||||
|
|
||||||
errorMessage: ObservableProperty<string> = new ObservableProperty<string>("");
|
errorMessage: ObservableProperty<string> = new ObservableProperty<string>("");
|
||||||
alertMessage: ObservableProperty<string> = new ObservableProperty<string>("");
|
alertMessage: ObservableProperty<string> = new ObservableProperty<string>("");
|
||||||
pedalBoard: ObservableProperty<PedalBoard> = new ObservableProperty<PedalBoard>(new PedalBoard());
|
pedalBoard: ObservableProperty<PedalBoard> = new ObservableProperty<PedalBoard>(new PedalBoard());
|
||||||
@@ -384,6 +405,7 @@ class PiPedalModelImpl implements PiPedalModel {
|
|||||||
new PresetIndex()
|
new PresetIndex()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined> = new ObservableProperty<ZoomedControlInfo | undefined>(undefined);
|
||||||
|
|
||||||
svgImgUrl(svgImage: string): string {
|
svgImgUrl(svgImage: string): string {
|
||||||
//return this.varServerUrl + "img/" + svgImage;
|
//return this.varServerUrl + "img/" + svgImage;
|
||||||
@@ -406,8 +428,10 @@ class PiPedalModelImpl implements PiPedalModel {
|
|||||||
this.onSocketMessage = this.onSocketMessage.bind(this);
|
this.onSocketMessage = this.onSocketMessage.bind(this);
|
||||||
this.onSocketReconnecting = this.onSocketReconnecting.bind(this);
|
this.onSocketReconnecting = this.onSocketReconnecting.bind(this);
|
||||||
this.onSocketReconnected = this.onSocketReconnected.bind(this);
|
this.onSocketReconnected = this.onSocketReconnected.bind(this);
|
||||||
|
this.onVisibilityChanged = this.onVisibilityChanged.bind(this);
|
||||||
}
|
}
|
||||||
onSocketReconnecting(retry: number, maxRetries: number): void {
|
onSocketReconnecting(retry: number, maxRetries: number): void {
|
||||||
|
if (this.visibilityState.get() === VisibilityState.Hidden) return;
|
||||||
//if (retry !== 0) {
|
//if (retry !== 0) {
|
||||||
this.setState(State.Reconnecting);
|
this.setState(State.Reconnecting);
|
||||||
//}
|
//}
|
||||||
@@ -415,9 +439,12 @@ class PiPedalModelImpl implements PiPedalModel {
|
|||||||
|
|
||||||
|
|
||||||
onSocketError(errorMessage: string, exception: any) {
|
onSocketError(errorMessage: string, exception: any) {
|
||||||
|
if (this.visibilityState.get() === VisibilityState.Hidden) return;
|
||||||
this.onError(errorMessage);
|
this.onError(errorMessage);
|
||||||
}
|
}
|
||||||
onSocketMessage(header: PiPedalMessageHeader, body?: any) {
|
onSocketMessage(header: PiPedalMessageHeader, body?: any) {
|
||||||
|
if (this.visibilityState.get() === VisibilityState.Hidden) return;
|
||||||
|
|
||||||
let message = header.message;
|
let message = header.message;
|
||||||
if (message === "onMonitorPortOutput") {
|
if (message === "onMonitorPortOutput") {
|
||||||
let monitorPortOutputBody = body as MonitorPortOutputBody;
|
let monitorPortOutputBody = body as MonitorPortOutputBody;
|
||||||
@@ -557,6 +584,8 @@ class PiPedalModelImpl implements PiPedalModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSocketReconnected() {
|
onSocketReconnected() {
|
||||||
|
if (this.visibilityState.get() === VisibilityState.Hidden) return;
|
||||||
|
|
||||||
// reload state, but not configuration.
|
// reload state, but not configuration.
|
||||||
this.getWebSocket().request<number>("hello")
|
this.getWebSocket().request<number>("hello")
|
||||||
.then(clientId => {
|
.then(clientId => {
|
||||||
@@ -771,9 +800,52 @@ class PiPedalModelImpl implements PiPedalModel {
|
|||||||
this.state.set(State.Error);
|
this.state.set(State.Error);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exitBackgroundState()
|
||||||
|
{
|
||||||
|
if (this.state.get() === State.Background)
|
||||||
|
{
|
||||||
|
console.log("Exiting background state.");
|
||||||
|
this.visibilityState.set(VisibilityState.Visible);
|
||||||
|
this.webSocket?.exitBackgroundState();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
enterBackgroundState()
|
||||||
|
{
|
||||||
|
if (this.state.get() !== State.Background)
|
||||||
|
{
|
||||||
|
console.log("Entering background state.");
|
||||||
|
this.visibilityState.set(VisibilityState.Hidden);
|
||||||
|
this.setState(State.Background);
|
||||||
|
this.webSocket?.enterBackgroundState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onVisibilityChanged(doc: Document, event: Event) : any
|
||||||
|
{
|
||||||
|
if (document.visibilityState) {
|
||||||
|
|
||||||
|
switch (document.visibilityState)
|
||||||
|
{
|
||||||
|
case "visible":
|
||||||
|
this.visibilityState.set(VisibilityState.Visible);
|
||||||
|
this.exitBackgroundState();
|
||||||
|
break;
|
||||||
|
case "hidden":
|
||||||
|
this.visibilityState.set(VisibilityState.Hidden);
|
||||||
|
this.enterBackgroundState();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
initialize(): void {
|
initialize(): void {
|
||||||
this.setError("");
|
this.setError("");
|
||||||
this.setState(State.Loading);
|
this.setState(State.Loading);
|
||||||
|
|
||||||
this.requestConfig()
|
this.requestConfig()
|
||||||
.then((succeeded) => {
|
.then((succeeded) => {
|
||||||
if (succeeded) {
|
if (succeeded) {
|
||||||
@@ -783,6 +855,11 @@ class PiPedalModelImpl implements PiPedalModel {
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.setError("Failed to get server state. \n\n" + error);
|
this.setError("Failed to get server state. \n\n" + error);
|
||||||
});
|
});
|
||||||
|
let t = this.onVisibilityChanged;
|
||||||
|
|
||||||
|
(document as any).addEventListener("visibilitychange",(doc: Document,event: Event) => {
|
||||||
|
return t(doc,event);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getControl(uiPlugin: UiPlugin, portSymbol: string): UiControl | null {
|
getControl(uiPlugin: UiPlugin, portSymbol: string): UiControl | null {
|
||||||
@@ -1598,6 +1675,14 @@ class PiPedalModelImpl implements PiPedalModel {
|
|||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
zoomUiControl(sourceElement: HTMLElement,instanceId: number,uiControl: UiControl): void
|
||||||
|
{
|
||||||
|
this.zoomedUiControl.set({source: sourceElement,instanceId: instanceId,uiControl: uiControl});
|
||||||
|
}
|
||||||
|
clearZoomedControl(): void
|
||||||
|
{
|
||||||
|
this.zoomedUiControl.set(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class PiPedalSocket {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.onError) {
|
if (this.onError) {
|
||||||
this.onError("Invalid server response. " + error, error);
|
this.onError("Invalid server response. " + error, error as Error);
|
||||||
} else {
|
} else {
|
||||||
throw new PiPedalStateError("Invalid server response.");
|
throw new PiPedalStateError("Invalid server response.");
|
||||||
}
|
}
|
||||||
@@ -189,6 +189,9 @@ class PiPedalSocket {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
this._reconnect();
|
||||||
|
}
|
||||||
|
_reconnect() {
|
||||||
this._discardReplyReservations();
|
this._discardReplyReservations();
|
||||||
this.retrying = true;
|
this.retrying = true;
|
||||||
this.retryCount = 0;
|
this.retryCount = 0;
|
||||||
@@ -199,6 +202,20 @@ class PiPedalSocket {
|
|||||||
this.reconnect();
|
this.reconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isBackground: boolean = false;
|
||||||
|
|
||||||
|
enterBackgroundState()
|
||||||
|
{
|
||||||
|
this.isBackground = true;
|
||||||
|
this.socket?.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
exitBackgroundState()
|
||||||
|
{
|
||||||
|
this.isBackground = false;
|
||||||
|
this._reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
reconnect() {
|
reconnect() {
|
||||||
|
|
||||||
if (this.socket)
|
if (this.socket)
|
||||||
|
|||||||
+51
-71
@@ -24,9 +24,9 @@ import Typography from '@material-ui/core/Typography';
|
|||||||
import Input from '@material-ui/core/Input';
|
import Input from '@material-ui/core/Input';
|
||||||
import Select from '@material-ui/core/Select';
|
import Select from '@material-ui/core/Select';
|
||||||
import Switch from '@material-ui/core/Switch';
|
import Switch from '@material-ui/core/Switch';
|
||||||
import Units from './Units';
|
|
||||||
import Utility, {nullCast} from './Utility';
|
import Utility, {nullCast} from './Utility';
|
||||||
import MenuItem from '@material-ui/core/MenuItem';
|
import MenuItem from '@material-ui/core/MenuItem';
|
||||||
|
import {PiPedalModel,PiPedalModelFactory} from './PiPedalModel';
|
||||||
|
|
||||||
|
|
||||||
const MIN_ANGLE = -135;
|
const MIN_ANGLE = -135;
|
||||||
@@ -78,6 +78,7 @@ const styles = (theme: Theme) => createStyles({
|
|||||||
|
|
||||||
export interface PluginControlProps extends WithStyles<typeof styles> {
|
export interface PluginControlProps extends WithStyles<typeof styles> {
|
||||||
uiControl?: UiControl;
|
uiControl?: UiControl;
|
||||||
|
instanceId: number;
|
||||||
value: number;
|
value: number;
|
||||||
onPreviewChange?: (value: number) => void;
|
onPreviewChange?: (value: number) => void;
|
||||||
onChange: (value: number) => void;
|
onChange: (value: number) => void;
|
||||||
@@ -92,11 +93,15 @@ const PluginControl =
|
|||||||
withStyles(styles, { withTheme: true })(
|
withStyles(styles, { withTheme: true })(
|
||||||
class extends Component<PluginControlProps, PluginControlState>
|
class extends Component<PluginControlProps, PluginControlState>
|
||||||
{
|
{
|
||||||
|
|
||||||
|
frameRef: React.RefObject<HTMLDivElement>;
|
||||||
imgRef: React.RefObject<HTMLImageElement>;
|
imgRef: React.RefObject<HTMLImageElement>;
|
||||||
inputRef: React.RefObject<HTMLInputElement>;
|
inputRef: React.RefObject<HTMLInputElement>;
|
||||||
selectRef: React.RefObject<HTMLSelectElement>;
|
selectRef: React.RefObject<HTMLSelectElement>;
|
||||||
|
|
||||||
displayValueRef: React.RefObject<HTMLDivElement>;
|
displayValueRef: React.RefObject<HTMLDivElement>;
|
||||||
|
model: PiPedalModel;
|
||||||
|
|
||||||
|
|
||||||
constructor(props: PluginControlProps) {
|
constructor(props: PluginControlProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -104,11 +109,12 @@ const PluginControl =
|
|||||||
this.state = {
|
this.state = {
|
||||||
error: false
|
error: false
|
||||||
};
|
};
|
||||||
|
this.model = PiPedalModelFactory.getInstance();
|
||||||
this.imgRef = React.createRef();
|
this.imgRef = React.createRef();
|
||||||
this.inputRef = React.createRef();
|
this.inputRef = React.createRef();
|
||||||
this.selectRef = React.createRef();
|
this.selectRef = React.createRef();
|
||||||
this.displayValueRef = React.createRef();
|
this.displayValueRef = React.createRef();
|
||||||
|
this.frameRef = React.createRef();
|
||||||
|
|
||||||
|
|
||||||
this.onPointerDown = this.onPointerDown.bind(this);
|
this.onPointerDown = this.onPointerDown.bind(this);
|
||||||
@@ -127,7 +133,29 @@ const PluginControl =
|
|||||||
this.onInputFocus = this.onInputFocus.bind(this);
|
this.onInputFocus = this.onInputFocus.bind(this);
|
||||||
this.onInputKeyPress = this.onInputKeyPress.bind(this);
|
this.onInputKeyPress = this.onInputKeyPress.bind(this);
|
||||||
}
|
}
|
||||||
|
isTouchDevice(): boolean {
|
||||||
|
return Utility.isTouchDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
showZoomedControl()
|
||||||
|
{
|
||||||
|
if (this.props.uiControl && this.frameRef.current)
|
||||||
|
{
|
||||||
|
this.model.zoomUiControl(this.frameRef.current,this.props.instanceId,this.props.uiControl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hideZoomedControl()
|
||||||
|
{
|
||||||
|
if (this.frameRef.current && this.model.zoomedUiControl.get()?.source === this.frameRef.current)
|
||||||
|
{
|
||||||
|
this.model.clearZoomedControl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount()
|
||||||
|
{
|
||||||
|
this.hideZoomedControl();
|
||||||
|
}
|
||||||
inputChanged: boolean = false;
|
inputChanged: boolean = false;
|
||||||
|
|
||||||
onInputLostFocus(event: any): void {
|
onInputLostFocus(event: any): void {
|
||||||
@@ -190,11 +218,12 @@ const PluginControl =
|
|||||||
// clamp and quantize.
|
// clamp and quantize.
|
||||||
let range = this.valueToRange(result);
|
let range = this.valueToRange(result);
|
||||||
result = this.rangeToValue(range);
|
result = this.rangeToValue(range);
|
||||||
|
let displayVal = this.props.uiControl?.formatShortValue(result)??"";
|
||||||
if (event.currentTarget) {
|
if (event.currentTarget) {
|
||||||
event.currentTarget.value = this.formatValue(this.props.uiControl, result);
|
event.currentTarget.value = displayVal;
|
||||||
}
|
}
|
||||||
this.previewInputValue(result, true);
|
this.previewInputValue(result, true);
|
||||||
this.inputRef.current.value = this.formatValue(this.props.uiControl, result); // no rerender because the value won't change.
|
this.inputRef.current.value = displayVal; // no rerender because the value won't change.
|
||||||
} else {
|
} else {
|
||||||
this.setState({ error: !valid });
|
this.setState({ error: !valid });
|
||||||
if (valid) {
|
if (valid) {
|
||||||
@@ -266,12 +295,21 @@ const PluginControl =
|
|||||||
|
|
||||||
onPointerDown(e: PointerEvent<HTMLImageElement>): void {
|
onPointerDown(e: PointerEvent<HTMLImageElement>): void {
|
||||||
if (!this.mouseDown && this.isValidPointer(e)) {
|
if (!this.mouseDown && this.isValidPointer(e)) {
|
||||||
++this.pointersDown;
|
|
||||||
|
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (this.isTouchDevice())
|
||||||
|
{
|
||||||
|
if (this.props.uiControl?.isDial()??false)
|
||||||
|
{
|
||||||
|
this.showZoomedControl();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++this.pointersDown;
|
||||||
|
|
||||||
|
|
||||||
this.mouseDown = true;
|
this.mouseDown = true;
|
||||||
|
|
||||||
this.pointerId = e.pointerId;
|
this.pointerId = e.pointerId;
|
||||||
@@ -443,7 +481,7 @@ const PluginControl =
|
|||||||
}
|
}
|
||||||
let inputElement = this.inputRef.current;
|
let inputElement = this.inputRef.current;
|
||||||
if (inputElement) {
|
if (inputElement) {
|
||||||
let v = this.formatValue(this.props.uiControl, value);
|
let v = this.props.uiControl?.formatShortValue(value)??"";
|
||||||
inputElement.value = v;
|
inputElement.value = v;
|
||||||
}
|
}
|
||||||
let displayValue = this.displayValueRef.current;
|
let displayValue = this.displayValueRef.current;
|
||||||
@@ -528,69 +566,10 @@ const PluginControl =
|
|||||||
|
|
||||||
formatDisplayValue(uiControl: UiControl | undefined, value: number): string {
|
formatDisplayValue(uiControl: UiControl | undefined, value: number): string {
|
||||||
if (!uiControl) return "";
|
if (!uiControl) return "";
|
||||||
for (let i = 0; i < uiControl.scale_points.length; ++i)
|
|
||||||
{
|
|
||||||
let scalePoint = uiControl.scale_points[i];
|
|
||||||
if (scalePoint.value === value)
|
|
||||||
{
|
|
||||||
return scalePoint.label;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let text = this.formatValue(uiControl, value);
|
|
||||||
|
|
||||||
switch (uiControl.units) {
|
return uiControl.formatDisplayValue(value);
|
||||||
case Units.bpm:
|
|
||||||
text += "bpm";
|
|
||||||
break;
|
|
||||||
case Units.cent:
|
|
||||||
text += "cents";
|
|
||||||
break;
|
|
||||||
case Units.cm:
|
|
||||||
text += "cm";
|
|
||||||
break;
|
|
||||||
case Units.db:
|
|
||||||
text += "dB";
|
|
||||||
break;
|
|
||||||
case Units.hz:
|
|
||||||
text += "Hz";
|
|
||||||
break;
|
|
||||||
case Units.khz:
|
|
||||||
text += "kHz";
|
|
||||||
break;
|
|
||||||
case Units.km:
|
|
||||||
text += "km";
|
|
||||||
break;
|
|
||||||
case Units.m:
|
|
||||||
text += "m";
|
|
||||||
break;
|
|
||||||
case Units.mhz:
|
|
||||||
text += "MHz";
|
|
||||||
break;
|
|
||||||
case Units.min:
|
|
||||||
text += "min";
|
|
||||||
break;
|
|
||||||
case Units.ms:
|
|
||||||
text += "ms";
|
|
||||||
break;
|
|
||||||
case Units.pc:
|
|
||||||
text += "%";
|
|
||||||
break;
|
|
||||||
case Units.s:
|
|
||||||
text += "s";
|
|
||||||
break;
|
|
||||||
// Midinote: not handled.
|
|
||||||
// semitone12TET not handled.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
formatValue(uiControl: UiControl | undefined, value: number): string {
|
|
||||||
if (!uiControl) return "";
|
|
||||||
return uiControl.formatValue(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
valueToRange(value: number): number {
|
valueToRange(value: number): number {
|
||||||
@@ -681,7 +660,7 @@ const PluginControl =
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-start", width: item_width, margin: 8 }}>
|
<div ref={this.frameRef} style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-start", width: item_width, margin: 8 }}>
|
||||||
{/* TITLE SECTION */}
|
{/* TITLE SECTION */}
|
||||||
<div style={{ flex: "0 0 auto", width: "100%", marginBottom: 8,marginLeft: isSelect? 16: 0, marginRight: 0 }}>
|
<div style={{ flex: "0 0 auto", width: "100%", marginBottom: 8,marginLeft: isSelect? 16: 0, marginRight: 0 }}>
|
||||||
<Typography variant="caption" display="block" noWrap style={{
|
<Typography variant="caption" display="block" noWrap style={{
|
||||||
@@ -697,7 +676,8 @@ const PluginControl =
|
|||||||
) : (
|
) : (
|
||||||
<div style={{ flex: "0 1 auto" }}>
|
<div style={{ flex: "0 1 auto" }}>
|
||||||
<img ref={this.imgRef} src="img/fx_dial.svg"
|
<img ref={this.imgRef} src="img/fx_dial.svg"
|
||||||
style={{ overscrollBehavior: "none", touchAction: "none", width: 36, height: 36, opacity: DEFAULT_OPACITY, transform: this.getRotationTransform() }}
|
style={{ overscrollBehavior: "none", touchAction: "none",
|
||||||
|
width: 36, height: 36, opacity: DEFAULT_OPACITY, transform: this.getRotationTransform() }}
|
||||||
draggable="true"
|
draggable="true"
|
||||||
onTouchStart={this.onTouchStart} onTouchMove={this.onTouchMove}
|
onTouchStart={this.onTouchStart} onTouchMove={this.onTouchMove}
|
||||||
onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} onPointerMoveCapture={this.onPointerMove} onDrag={this.onDrag}
|
onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} onPointerMoveCapture={this.onPointerMove} onDrag={this.onDrag}
|
||||||
@@ -720,7 +700,7 @@ const PluginControl =
|
|||||||
<div>
|
<div>
|
||||||
<Input key={value}
|
<Input key={value}
|
||||||
type="number"
|
type="number"
|
||||||
defaultValue={this.formatValue(control, value)}
|
defaultValue={control.formatShortValue(value)}
|
||||||
error={this.state.error}
|
error={this.state.error}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
'aria-label':
|
'aria-label':
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ const PluginControlView =
|
|||||||
}
|
}
|
||||||
return ((
|
return ((
|
||||||
|
|
||||||
<PluginControl uiControl={uiControl} value={controlValue.value}
|
<PluginControl instanceId={this.props.instanceId} uiControl={uiControl} value={controlValue.value}
|
||||||
onChange={(value: number) => { this.onValueChanged(controlValue!.key, value) }}
|
onChange={(value: number) => { this.onValueChanged(controlValue!.key, value) }}
|
||||||
onPreviewChange={(value: number) => { this.onPreviewChange(controlValue!.key, value) }}
|
onPreviewChange={(value: number) => { this.onPreviewChange(controlValue!.key, value) }}
|
||||||
requestIMEEdit={(uiControl, value) => this.requestImeEdit(uiControl, value)}
|
requestIMEEdit={(uiControl, value) => this.requestImeEdit(uiControl, value)}
|
||||||
@@ -353,7 +353,7 @@ const PluginControlView =
|
|||||||
|
|
||||||
|
|
||||||
let controls = shortList.map((controlValue) => (
|
let controls = shortList.map((controlValue) => (
|
||||||
<PluginControl uiControl={plugin.getControl(controlValue.key)} value={controlValue.value}
|
<PluginControl instanceId={this.props.instanceId} uiControl={plugin.getControl(controlValue.key)} value={controlValue.value}
|
||||||
onChange={(value: number) => { this.onValueChanged(controlValue.key, value) }}
|
onChange={(value: number) => { this.onValueChanged(controlValue.key, value) }}
|
||||||
onPreviewChange={(value: number) => { this.onPreviewChange(controlValue.key, value) }}
|
onPreviewChange={(value: number) => { this.onPreviewChange(controlValue.key, value) }}
|
||||||
requestIMEEdit={(uiControl, value) => this.requestImeEdit(uiControl, value)}
|
requestIMEEdit={(uiControl, value) => this.requestImeEdit(uiControl, value)}
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ const SplitControlView =
|
|||||||
</div>
|
</div>
|
||||||
<div className={gridClass} >
|
<div className={gridClass} >
|
||||||
<div style={{ flex: "0 0 auto" }}>
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
<PluginControl uiControl={SplitTypeControl} value={typeValue.value}
|
<PluginControl instanceId={this.props.instanceId} uiControl={SplitTypeControl} value={typeValue.value}
|
||||||
onChange={(value: number) => { this.onValueChanged(typeValue.key, value) }}
|
onChange={(value: number) => { this.onValueChanged(typeValue.key, value) }}
|
||||||
onPreviewChange={(value: number) => { this.onPreviewChange(typeValue.key, value) }}
|
onPreviewChange={(value: number) => { this.onPreviewChange(typeValue.key, value) }}
|
||||||
requestIMEEdit={(uiControl, value) => { }}
|
requestIMEEdit={(uiControl, value) => { }}
|
||||||
@@ -195,7 +195,7 @@ const SplitControlView =
|
|||||||
{
|
{
|
||||||
typeValue.value === 0 && (
|
typeValue.value === 0 && (
|
||||||
<div style={{ flex: "0 0 auto" }}>
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
<PluginControl uiControl={SplitAbControl} value={selectValue.value}
|
<PluginControl instanceId={this.props.instanceId} uiControl={SplitAbControl} value={selectValue.value}
|
||||||
onChange={(value: number) => { this.onValueChanged(selectValue.key, value) }}
|
onChange={(value: number) => { this.onValueChanged(selectValue.key, value) }}
|
||||||
onPreviewChange={(value: number) => { this.onPreviewChange(selectValue.key, value) }}
|
onPreviewChange={(value: number) => { this.onPreviewChange(selectValue.key, value) }}
|
||||||
requestIMEEdit={(uiControl, value) => { }}
|
requestIMEEdit={(uiControl, value) => { }}
|
||||||
@@ -208,7 +208,7 @@ const SplitControlView =
|
|||||||
{
|
{
|
||||||
typeValue.value === 1 && (
|
typeValue.value === 1 && (
|
||||||
<div style={{ flex: "0 0 auto" }}>
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
<PluginControl uiControl={SplitMixControl} value={mixValue.value}
|
<PluginControl instanceId={this.props.instanceId} uiControl={SplitMixControl} value={mixValue.value}
|
||||||
onChange={(value: number) => { this.onValueChanged(mixValue.key, value) }}
|
onChange={(value: number) => { this.onValueChanged(mixValue.key, value) }}
|
||||||
onPreviewChange={(value: number) => { this.onPreviewChange(mixValue.key, value) }}
|
onPreviewChange={(value: number) => { this.onPreviewChange(mixValue.key, value) }}
|
||||||
requestIMEEdit={(uiControl, value) => { }}
|
requestIMEEdit={(uiControl, value) => { }}
|
||||||
@@ -221,7 +221,7 @@ const SplitControlView =
|
|||||||
{
|
{
|
||||||
typeValue.value === 2 && (
|
typeValue.value === 2 && (
|
||||||
<div style={{ flex: "0 0 auto" }}>
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
<PluginControl uiControl={SplitPanLeftControl} value={panLValue.value}
|
<PluginControl instanceId={this.props.instanceId} uiControl={SplitPanLeftControl} value={panLValue.value}
|
||||||
onChange={(value: number) => { this.onValueChanged(panLValue.key, value) }}
|
onChange={(value: number) => { this.onValueChanged(panLValue.key, value) }}
|
||||||
onPreviewChange={(value: number) => { this.onPreviewChange(panLValue.key, value) }}
|
onPreviewChange={(value: number) => { this.onPreviewChange(panLValue.key, value) }}
|
||||||
requestIMEEdit={(uiControl, value) => { }}
|
requestIMEEdit={(uiControl, value) => { }}
|
||||||
@@ -233,7 +233,7 @@ const SplitControlView =
|
|||||||
{
|
{
|
||||||
typeValue.value === 2 && (
|
typeValue.value === 2 && (
|
||||||
<div style={{ flex: "0 0 auto" }}>
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
<PluginControl uiControl={SplitVolLeftControl} value={volumeLValue.value}
|
<PluginControl instanceId={this.props.instanceId} uiControl={SplitVolLeftControl} value={volumeLValue.value}
|
||||||
onChange={(value: number) => { this.onValueChanged(volumeLValue.key, value) }}
|
onChange={(value: number) => { this.onValueChanged(volumeLValue.key, value) }}
|
||||||
onPreviewChange={(value: number) => { this.onPreviewChange(volumeLValue.key, value) }}
|
onPreviewChange={(value: number) => { this.onPreviewChange(volumeLValue.key, value) }}
|
||||||
requestIMEEdit={(uiControl, value) => { }}
|
requestIMEEdit={(uiControl, value) => { }}
|
||||||
@@ -245,7 +245,7 @@ const SplitControlView =
|
|||||||
{
|
{
|
||||||
typeValue.value === 2 && (
|
typeValue.value === 2 && (
|
||||||
<div style={{ flex: "0 0 auto" }}>
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
<PluginControl uiControl={SplitPanRightControl} value={panRValue.value}
|
<PluginControl instanceId={this.props.instanceId} uiControl={SplitPanRightControl} value={panRValue.value}
|
||||||
onChange={(value: number) => { this.onValueChanged(panRValue.key, value) }}
|
onChange={(value: number) => { this.onValueChanged(panRValue.key, value) }}
|
||||||
onPreviewChange={(value: number) => { this.onPreviewChange(panRValue.key, value) }}
|
onPreviewChange={(value: number) => { this.onPreviewChange(panRValue.key, value) }}
|
||||||
requestIMEEdit={(uiControl, value) => { }}
|
requestIMEEdit={(uiControl, value) => { }}
|
||||||
@@ -257,7 +257,7 @@ const SplitControlView =
|
|||||||
{
|
{
|
||||||
typeValue.value === 2 && (
|
typeValue.value === 2 && (
|
||||||
<div style={{ flex: "0 0 auto" }}>
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
<PluginControl uiControl={SplitVolRightControl} value={volumeRValue.value}
|
<PluginControl instanceId={this.props.instanceId} uiControl={SplitVolRightControl} value={volumeRValue.value}
|
||||||
onChange={(value: number) => { this.onValueChanged(volumeRValue.key, value) }}
|
onChange={(value: number) => { this.onValueChanged(volumeRValue.key, value) }}
|
||||||
onPreviewChange={(value: number) => { this.onPreviewChange(volumeRValue.key, value) }}
|
onPreviewChange={(value: number) => { this.onPreviewChange(volumeRValue.key, value) }}
|
||||||
requestIMEEdit={(uiControl, value) => { }}
|
requestIMEEdit={(uiControl, value) => { }}
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ export interface ControlEntry {
|
|||||||
|
|
||||||
const Utility = class {
|
const Utility = class {
|
||||||
static isTouchDevice(): boolean {
|
static isTouchDevice(): boolean {
|
||||||
return navigator.maxTouchPoints > 0;
|
return (('ontouchstart' in window) &&
|
||||||
|
((navigator.maxTouchPoints??0) > 0) );
|
||||||
}
|
}
|
||||||
|
|
||||||
static hasIMEKeyboard(): boolean {
|
static hasIMEKeyboard(): boolean {
|
||||||
|
|||||||
+11
-1
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { createStyles, withStyles, WithStyles, Theme } from '@material-ui/core/styles';
|
import { createStyles, withStyles, WithStyles, Theme } from '@material-ui/core/styles';
|
||||||
import { PiPedalModel, PiPedalModelFactory, VuUpdateInfo, VuSubscriptionHandle } from './PiPedalModel';
|
import { PiPedalModel, State,PiPedalModelFactory, VuUpdateInfo, VuSubscriptionHandle } from './PiPedalModel';
|
||||||
|
|
||||||
|
|
||||||
const DISPLAY_HEIGHT = 110;
|
const DISPLAY_HEIGHT = 110;
|
||||||
@@ -226,6 +226,7 @@ export const VuMeter =
|
|||||||
this.yYellow = dbToY(DB_YELLOW);
|
this.yYellow = dbToY(DB_YELLOW);
|
||||||
|
|
||||||
this.onVuChanged = this.onVuChanged.bind(this);
|
this.onVuChanged = this.onVuChanged.bind(this);
|
||||||
|
this.onStateChanged = this.onStateChanged.bind(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,13 +402,22 @@ export const VuMeter =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onStateChanged(state: State) {
|
||||||
|
// initial connection or reconnect
|
||||||
|
if (state === State.Ready)
|
||||||
|
{
|
||||||
|
this.addVuSubscription(); // re-subscribe.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.model.state.addOnChangedHandler(this.onStateChanged);
|
||||||
|
|
||||||
this.addVuSubscription();
|
this.addVuSubscription();
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.removeVuSubscription();
|
this.removeVuSubscription();
|
||||||
|
this.model.state.removeOnChangedHandler(this.onStateChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,369 @@
|
|||||||
|
// Copyright (c) 2021 Robin Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import React, { TouchEvent, PointerEvent, SyntheticEvent } from 'react'; import { Theme, withStyles, WithStyles, createStyles } from '@material-ui/core/styles';
|
||||||
|
import { PiPedalModel, PiPedalModelFactory, ZoomedControlInfo } from './PiPedalModel';
|
||||||
|
|
||||||
|
const SELECTED_OPACITY = 0.8;
|
||||||
|
const DEFAULT_OPACITY = 0.6;
|
||||||
|
const FINE_RANGE_SCALE = 0.1; //
|
||||||
|
const ULTRA_FINE_RANGE_SCALE = 0.02; // 12000 pixels to move from 0 to 1.
|
||||||
|
|
||||||
|
const MIN_ANGLE = -135;
|
||||||
|
const MAX_ANGLE = 135;
|
||||||
|
const DEAD_ZONE_SIZE = 5;
|
||||||
|
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => createStyles({
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
interface ZoomedDialProps extends WithStyles<typeof styles> {
|
||||||
|
theme: Theme,
|
||||||
|
size: number,
|
||||||
|
|
||||||
|
controlInfo: ZoomedControlInfo | undefined,
|
||||||
|
|
||||||
|
onPreviewValue(value: number): void,
|
||||||
|
onSetValue(value: number): void
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ZoomedDialState {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const ZoomedDial = withStyles(styles, { withTheme: true })(
|
||||||
|
|
||||||
|
class extends React.Component<ZoomedDialProps, ZoomedDialState> {
|
||||||
|
|
||||||
|
model: PiPedalModel = PiPedalModelFactory.getInstance();
|
||||||
|
imgRef: React.RefObject<HTMLImageElement> = React.createRef();
|
||||||
|
|
||||||
|
constructor(props: ZoomedDialProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onTouchStart = this.onTouchStart.bind(this);
|
||||||
|
this.onTouchMove = this.onTouchMove.bind(this);
|
||||||
|
this.onBodyPointerDownCapture = this.onBodyPointerDownCapture.bind(this);
|
||||||
|
|
||||||
|
this.onPointerDown = this.onPointerDown.bind(this);
|
||||||
|
this.onPointerUp = this.onPointerUp.bind(this);
|
||||||
|
this.onPointerMove = this.onPointerMove.bind(this);
|
||||||
|
this.onPointerLostCapture = this.onPointerLostCapture.bind(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onPointerUp(e: PointerEvent<HTMLImageElement>) {
|
||||||
|
|
||||||
|
if (this.isCapturedPointer(e)) {
|
||||||
|
--this.pointersDown;
|
||||||
|
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
this.updateAngle(e);
|
||||||
|
this.commitAngle();
|
||||||
|
|
||||||
|
this.releaseCapture(e);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
--this.pointersDown;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
releaseCapture(e: PointerEvent<HTMLImageElement>)
|
||||||
|
{
|
||||||
|
let img = this.imgRef.current;
|
||||||
|
|
||||||
|
if (img && img.style) {
|
||||||
|
img.releasePointerCapture(e.pointerId);
|
||||||
|
img.style.opacity = "" + DEFAULT_OPACITY;
|
||||||
|
// they get automaticlly released.
|
||||||
|
// for (let i = 0; i < this.capturedPointers.length; ++i)
|
||||||
|
// {
|
||||||
|
// img.releasePointerCapture (this.capturedPointers[i]);
|
||||||
|
// }
|
||||||
|
this.capturedPointers = [];
|
||||||
|
}
|
||||||
|
document.body.removeEventListener(
|
||||||
|
"pointerdown",
|
||||||
|
this.onBodyPointerDownCapture,true
|
||||||
|
);
|
||||||
|
|
||||||
|
this.mouseDown = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onPointerLostCapture(e: PointerEvent<HTMLImageElement>) {
|
||||||
|
if (this.isCapturedPointer(e)) {
|
||||||
|
--this.pointersDown;
|
||||||
|
|
||||||
|
|
||||||
|
this.releaseCapture(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onTouchStart(e: TouchEvent<HTMLImageElement>) {
|
||||||
|
//must be defined to get onTouchMove
|
||||||
|
}
|
||||||
|
onTouchMove(e: TouchEvent<HTMLImageElement>) {
|
||||||
|
// e.preventDefault();
|
||||||
|
e.stopPropagation(); // cancels scroll!!!
|
||||||
|
|
||||||
|
}
|
||||||
|
capturedPointers: number[] = [];
|
||||||
|
|
||||||
|
isExtraTouch(e: PointerEvent): boolean {
|
||||||
|
return (this.mouseDown && this.pointerType === "touch" && e.pointerType === "touch" && e.pointerId !== this.pointerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseDown: boolean = false;
|
||||||
|
pointerId: number = -1;
|
||||||
|
pointerType: string = "";
|
||||||
|
startX: number = 0;
|
||||||
|
startY: number = 0;
|
||||||
|
currentAngle: number = 0;
|
||||||
|
lastPointerAngle: number = 0;
|
||||||
|
|
||||||
|
isValidPointer(e: PointerEvent<HTMLImageElement>): boolean {
|
||||||
|
if (e.pointerType === "mouse") {
|
||||||
|
return e.button === 0;
|
||||||
|
} else if (e.pointerType === "pen") {
|
||||||
|
return true;
|
||||||
|
} else if (e.pointerType === "touch") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultValue: number = 0;
|
||||||
|
|
||||||
|
getCurrentValue(): number {
|
||||||
|
if (this.props.controlInfo == null) {
|
||||||
|
return this.defaultValue;
|
||||||
|
} else {
|
||||||
|
let uiControl = this.props.controlInfo.uiControl;
|
||||||
|
let instanceId = this.props.controlInfo.instanceId;
|
||||||
|
if (instanceId === -1) return 0;
|
||||||
|
let pedalBoardItem = this.model.pedalBoard.get()?.getItem(instanceId);
|
||||||
|
let value: number = pedalBoardItem?.getControlValue(this.props.controlInfo.uiControl.symbol) ?? 0;
|
||||||
|
this.defaultValue = value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angleToValue(angle: number): number {
|
||||||
|
if (this.props.controlInfo) {
|
||||||
|
if (angle > MAX_ANGLE) angle = MAX_ANGLE;
|
||||||
|
if (angle < MIN_ANGLE) angle = MIN_ANGLE;
|
||||||
|
|
||||||
|
let r = (angle - MIN_ANGLE) / (MAX_ANGLE - MIN_ANGLE);
|
||||||
|
|
||||||
|
let uiControl = this.props.controlInfo.uiControl;
|
||||||
|
|
||||||
|
return r * (uiControl.max_value-uiControl.min_value)+uiControl.min_value;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
valueToAngle(value: number): number {
|
||||||
|
if (this.props.controlInfo) {
|
||||||
|
let uiControl = this.props.controlInfo.uiControl;
|
||||||
|
let range = (value - uiControl.min_value) / (uiControl.max_value - uiControl.min_value);
|
||||||
|
if (range < 0) range = 0;
|
||||||
|
if (range > 1) range = 1;
|
||||||
|
return (MAX_ANGLE - MIN_ANGLE) * range + MIN_ANGLE;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointerToAngle(e: PointerEvent<HTMLImageElement>): number {
|
||||||
|
|
||||||
|
if (this.imgRef.current) {
|
||||||
|
let img = this.imgRef.current;
|
||||||
|
let rc = img.getBoundingClientRect();
|
||||||
|
let x = e.clientX - (rc.left+ rc.width / 2);
|
||||||
|
let y = e.clientY - (rc.top + rc.height / 2);
|
||||||
|
|
||||||
|
// dead zone in center of image.
|
||||||
|
|
||||||
|
if (x*x+y*y < DEAD_ZONE_SIZE*DEAD_ZONE_SIZE) return NaN;
|
||||||
|
|
||||||
|
let angle = -Math.atan2(-y, x) * 180 / Math.PI + 90;
|
||||||
|
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
return NaN;
|
||||||
|
}
|
||||||
|
captureElement?: HTMLElement;
|
||||||
|
pointersDown: number = 0;
|
||||||
|
|
||||||
|
onPointerDown(e: PointerEvent<HTMLImageElement>): void {
|
||||||
|
if (!this.mouseDown && this.isValidPointer(e)) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this.mouseDown = true;
|
||||||
|
this.pointersDown = 1;
|
||||||
|
this.pointerId = e.pointerId;
|
||||||
|
this.pointerType = e.pointerType;
|
||||||
|
this.startX = e.clientX;
|
||||||
|
this.startY = e.clientY;
|
||||||
|
this.currentAngle = this.valueToAngle(this.getCurrentValue());
|
||||||
|
this.lastPointerAngle = this.pointerToAngle(e);
|
||||||
|
|
||||||
|
let img = this.imgRef.current;
|
||||||
|
|
||||||
|
if (img) {
|
||||||
|
this.captureElement = img;
|
||||||
|
document.body.addEventListener(
|
||||||
|
"pointerdown",
|
||||||
|
this.onBodyPointerDownCapture, true
|
||||||
|
);
|
||||||
|
|
||||||
|
img.setPointerCapture(e.pointerId);
|
||||||
|
if (img.style) {
|
||||||
|
img.style.opacity = "" + SELECTED_OPACITY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (this.isExtraTouch(e)) {
|
||||||
|
++this.pointersDown;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isCapturedPointer(e: PointerEvent<HTMLImageElement>): boolean {
|
||||||
|
return this.mouseDown
|
||||||
|
&& e.pointerId === this.pointerId
|
||||||
|
&& e.pointerType === this.pointerType;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onPointerMove(e: PointerEvent<HTMLImageElement>): void {
|
||||||
|
if (this.isCapturedPointer(e)) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.updateAngle(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
commitAngle() {
|
||||||
|
let previewValue = this.angleToValue(this.currentAngle);
|
||||||
|
this.props.onSetValue(previewValue);
|
||||||
|
this.defaultValue = previewValue;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
updateAngle(e: PointerEvent<HTMLImageElement>): void {
|
||||||
|
let angle: number = this.pointerToAngle(e);
|
||||||
|
if (!isNaN(angle)) {
|
||||||
|
if (!isNaN(this.lastPointerAngle)) {
|
||||||
|
let dAngle = angle-this.lastPointerAngle;
|
||||||
|
while (dAngle > 180) dAngle -= 360;
|
||||||
|
while (dAngle < -180) dAngle += 360;
|
||||||
|
|
||||||
|
let scale = 1.0;
|
||||||
|
if (this.pointersDown === 2)
|
||||||
|
{
|
||||||
|
scale = FINE_RANGE_SCALE;
|
||||||
|
} else if (this.pointersDown >= 3)
|
||||||
|
{
|
||||||
|
scale = ULTRA_FINE_RANGE_SCALE;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentAngle += dAngle*scale;
|
||||||
|
let previewValue = this.angleToValue(this.currentAngle);
|
||||||
|
this.previewValue(previewValue);
|
||||||
|
this.props.onPreviewValue(previewValue);
|
||||||
|
}
|
||||||
|
this.lastPointerAngle = angle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
makeRotationTransform(angle: number): string
|
||||||
|
{
|
||||||
|
return "rotate(" + angle + "deg)";
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultRotationTransform(): string
|
||||||
|
{
|
||||||
|
let v = this.getCurrentValue();
|
||||||
|
let a = this.valueToAngle(v);
|
||||||
|
return this.makeRotationTransform(a);
|
||||||
|
}
|
||||||
|
previewValue(value: number): void
|
||||||
|
{
|
||||||
|
if (this.imgRef.current)
|
||||||
|
{
|
||||||
|
let img = this.imgRef.current;
|
||||||
|
let angle = this.valueToAngle(value);
|
||||||
|
img.style.transform = this.makeRotationTransform(angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onBodyPointerDownCapture(e_: any): any {
|
||||||
|
let e = e_ as PointerEvent;
|
||||||
|
if (this.isExtraTouch(e)) {
|
||||||
|
this.captureElement!.setPointerCapture(e.pointerId);
|
||||||
|
this.capturedPointers.push(e.pointerId);
|
||||||
|
++this.pointersDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
onDrag(e: SyntheticEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<img ref={this.imgRef} src="img/fx_dial.svg"
|
||||||
|
style={{
|
||||||
|
overscrollBehavior: "none", touchAction: "none",
|
||||||
|
width: this.props.size, height: this.props.size, opacity: DEFAULT_OPACITY,
|
||||||
|
transform: this.getDefaultRotationTransform()
|
||||||
|
}}
|
||||||
|
draggable="true"
|
||||||
|
onTouchStart={this.onTouchStart} onTouchMove={this.onTouchMove}
|
||||||
|
onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp}
|
||||||
|
onPointerMoveCapture={this.onPointerMove} onDrag={this.onDrag}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ZoomedDial;
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
// Copyright (c) 2021 Robin Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import React, {SyntheticEvent} from 'react';
|
||||||
|
import { Theme, withStyles, WithStyles,createStyles } from '@material-ui/core/styles';
|
||||||
|
import { ZoomedControlInfo } from './PiPedalModel';
|
||||||
|
import DialogEx from './DialogEx';
|
||||||
|
import { TransitionProps } from '@material-ui/core/transitions/transition';
|
||||||
|
import Slide from '@material-ui/core/Slide';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import { PiPedalModelFactory,PiPedalModel } from './PiPedalModel';
|
||||||
|
import ZoomedDial from './ZoomedDial';
|
||||||
|
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => createStyles({
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
interface ZoomedUiControlProps extends WithStyles<typeof styles> {
|
||||||
|
dialogOpen: boolean;
|
||||||
|
onDialogClose: () => void;
|
||||||
|
onDialogClosed: () => void;
|
||||||
|
controlInfo: ZoomedControlInfo | undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ZoomedUiControlState {
|
||||||
|
value: number
|
||||||
|
};
|
||||||
|
|
||||||
|
const Transition = React.forwardRef(function Transition(
|
||||||
|
props: TransitionProps & { children?: React.ReactElement },
|
||||||
|
ref: React.Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const ZoomedUiControl = withStyles(styles, { withTheme: true })(
|
||||||
|
|
||||||
|
class extends React.Component<ZoomedUiControlProps, ZoomedUiControlState> {
|
||||||
|
|
||||||
|
model: PiPedalModel;
|
||||||
|
|
||||||
|
constructor(props: ZoomedUiControlProps)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
this.model = PiPedalModelFactory.getInstance();
|
||||||
|
this.state = {
|
||||||
|
value: this.getCurrentValue()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentValue(): number {
|
||||||
|
if (!this.props.controlInfo)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
let uiControl = this.props.controlInfo.uiControl;
|
||||||
|
let instanceId = this.props.controlInfo.instanceId;
|
||||||
|
if (instanceId === -1) return 0;
|
||||||
|
let pedalBoardItem = this.model.pedalBoard.get()?.getItem(instanceId);
|
||||||
|
let value: number = pedalBoardItem?.getControlValue(uiControl.symbol)??0;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasControlChanged(oldProps: ZoomedUiControlProps, newProps: ZoomedUiControlProps)
|
||||||
|
{
|
||||||
|
if (oldProps.controlInfo === null)
|
||||||
|
{
|
||||||
|
return newProps.controlInfo !== null;
|
||||||
|
} else if (newProps.controlInfo === null)
|
||||||
|
{
|
||||||
|
return oldProps.controlInfo !== null;
|
||||||
|
} else {
|
||||||
|
if (newProps.controlInfo?.instanceId !== oldProps.controlInfo?.instanceId) return true;
|
||||||
|
if (newProps.controlInfo?.uiControl.symbol !== oldProps.controlInfo?.uiControl.symbol) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(oldProps: ZoomedUiControlProps, oldState: ZoomedUiControlState)
|
||||||
|
{
|
||||||
|
if (this.hasControlChanged(oldProps,this.props))
|
||||||
|
{
|
||||||
|
let currentValue = this.getCurrentValue();
|
||||||
|
if (this.state.value !== currentValue)
|
||||||
|
{
|
||||||
|
this.setState({value: this.getCurrentValue()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDrag(e: SyntheticEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let displayValue = this.props.controlInfo?.uiControl.formatDisplayValue(this.state.value)??"";
|
||||||
|
return (
|
||||||
|
<DialogEx tag="zoomedControlDlg" open={this.props.dialogOpen}
|
||||||
|
onClose={()=> { this.props.onDialogClose()}}
|
||||||
|
onAbort={()=> { this.props.onDialogClose()}}
|
||||||
|
onExited={ () => {
|
||||||
|
this.props.onDialogClosed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div style={{ width: 300, height: 300, background: "#FFF",
|
||||||
|
display: "flex", flexFlow: "column", alignItems: "center", alignContent: "center", justifyContent: "center"
|
||||||
|
|
||||||
|
}}
|
||||||
|
onDrag={this.onDrag}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
display="block" variant="h6" color="textPrimary" style={{textAlign: "center"}}
|
||||||
|
>
|
||||||
|
{this.props.controlInfo?.uiControl.name??""}
|
||||||
|
</Typography>
|
||||||
|
<ZoomedDial size={200} controlInfo={this.props.controlInfo}
|
||||||
|
onPreviewValue={(v)=> {
|
||||||
|
this.setState({value: v});
|
||||||
|
}}
|
||||||
|
onSetValue={(v)=> {
|
||||||
|
this.setState({value: v});
|
||||||
|
if (this.props.controlInfo)
|
||||||
|
{
|
||||||
|
this.model.setPedalBoardControlValue(
|
||||||
|
this.props.controlInfo.instanceId,
|
||||||
|
this.props.controlInfo.uiControl.symbol,
|
||||||
|
v);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
display="block" variant="h6" color="textPrimary" style={{textAlign: "center"}}
|
||||||
|
>
|
||||||
|
{displayValue}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</DialogEx>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ZoomedUiControl;
|
||||||
+5
-3
@@ -193,7 +193,7 @@ void Storage::LoadBank(int64_t instanceId)
|
|||||||
this->bankIndex.selectedBank(instanceId);
|
this->bankIndex.selectedBank(instanceId);
|
||||||
SaveBankIndex();
|
SaveBankIndex();
|
||||||
}
|
}
|
||||||
this->LoadPreset(this->bankIndex.selectedBank());
|
this->LoadPreset(this->currentBank.selectedPreset());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Storage::LoadCurrentBank()
|
void Storage::LoadCurrentBank()
|
||||||
@@ -368,8 +368,10 @@ bool Storage::LoadPreset(int64_t instanceId)
|
|||||||
{
|
{
|
||||||
if (!currentBank.hasItem(instanceId))
|
if (!currentBank.hasItem(instanceId))
|
||||||
return false;
|
return false;
|
||||||
currentBank.selectedPreset(instanceId);
|
if (instanceId != currentBank.selectedPreset()) {
|
||||||
SaveCurrentBank();
|
currentBank.selectedPreset(instanceId);
|
||||||
|
SaveCurrentBank();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void Storage::saveCurrentPreset(const PedalBoard &pedalBoard)
|
void Storage::saveCurrentPreset(const PedalBoard &pedalBoard)
|
||||||
|
|||||||
Reference in New Issue
Block a user