Touch tooltips, more tooltips

This commit is contained in:
Robin E. R. Davies
2025-06-22 16:12:03 -04:00
parent aff98b0c20
commit f95ecebb6b
20 changed files with 378 additions and 155 deletions
+1
View File
@@ -1,4 +1,5 @@
channel bindings help dialog. <ChannelBindingHelpDialog
Tooltips on touch ui? Tooltips on touch ui?
+3
View File
@@ -46,3 +46,6 @@ input[type=number] {
} }
img {
user-select: none;
}
+14 -10
View File
@@ -29,7 +29,7 @@ import { withStyles } from "tss-react/mui";
import WithStyles from './WithStyles'; import WithStyles from './WithStyles';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import IconButton from '@mui/material/IconButton'; import IconButtonEx from './IconButtonEx';
import VolunteerActivismIcon from '@mui/icons-material/VolunteerActivism'; import VolunteerActivismIcon from '@mui/icons-material/VolunteerActivism';
import MenuButton from '@mui/icons-material/Menu'; import MenuButton from '@mui/icons-material/Menu';
import { TemporaryDrawer } from './TemporaryDrawer'; import { TemporaryDrawer } from './TemporaryDrawer';
@@ -802,21 +802,23 @@ export
( (
<AppBar position="absolute" > <AppBar position="absolute" >
<Toolbar variant="dense" className={classes.toolBar} > <Toolbar variant="dense" className={classes.toolBar} >
<IconButton <IconButtonEx tooltip="Menu"
edge="start" edge="start"
aria-label="menu" aria-label="menu"
color="inherit" color="inherit"
onClick={() => { this.showDrawer() }} onClick={() => { this.showDrawer() }}
size="large"> size="large">
<MenuButton style={{ opacity: 0.75 }} /> <MenuButton style={{ opacity: 0.75 }} />
</IconButton> </IconButtonEx>
<div style={{ flex: "0 1 400px", minWidth: 100 }}> <div style={{ flex: "0 1 400px", minWidth: 100 }}>
<PresetSelector /> <PresetSelector />
</div> </div>
<div style={{ flex: "2 2 30px" }} /> <div style={{ flex: "2 2 30px" }} />
{this.state.canFullScreen && {this.state.canFullScreen &&
<IconButton <IconButtonEx
aria-label="menu" aria-label="maximise/minimise"
tooltip={this.state.isFullScreen ? "Exit full screen" : "Full screen"}
style={{ marginRight: 8, marginLeft: 8 }}
onClick={() => { this.toggleFullScreen(); }} onClick={() => { this.toggleFullScreen(); }}
color="inherit" color="inherit"
size="large"> size="large">
@@ -827,22 +829,24 @@ export
)} )}
</IconButton> </IconButtonEx>
} }
</Toolbar> </Toolbar>
</AppBar> </AppBar>
) : ( ) : (
<div className={classes.toolBarContent} > <div className={classes.toolBarContent} >
<IconButton <IconButtonEx
tooltip="Menu"
style={{ position: "absolute", left: 12, top: 8, zIndex: 2 }} style={{ position: "absolute", left: 12, top: 8, zIndex: 2 }}
aria-label="menu" aria-label="menu"
onClick={() => { this.showDrawer() }} onClick={() => { this.showDrawer() }}
color="inherit" color="inherit"
size="large"> size="large">
<MenuButton /> <MenuButton />
</IconButton> </IconButtonEx>
{this.state.canFullScreen && ( {this.state.canFullScreen && (
<IconButton <IconButtonEx
tooltip={this.state.isFullScreen ? "Exit full screen" : "Full screen"}
style={{ position: "absolute", right: 8, top: 8, zIndex: 2 }} style={{ position: "absolute", right: 8, top: 8, zIndex: 2 }}
aria-label="menu" aria-label="menu"
color="inherit" color="inherit"
@@ -855,7 +859,7 @@ export
)} )}
</IconButton> </IconButtonEx>
)} )}
</div> </div>
)} )}
+23 -11
View File
@@ -9,30 +9,42 @@ import ToolTipEx from './ToolTipEx'
interface ControlTooltipProps { interface ControlTooltipProps {
children: ReactElement, children: ReactElement,
uiControl: UiControl uiControl: UiControl
valueTooltip?: React.ReactNode;
} }
export default function ControlTooltip(props: ControlTooltipProps) { export default function ControlTooltip(props: ControlTooltipProps) {
let { children, uiControl } = props; let { children, uiControl, valueTooltip } = props;
if (uiControl.comment && (uiControl.comment !== uiControl.name)) { if (uiControl.comment && (uiControl.comment !== uiControl.name)) {
return ( return (
<ToolTipEx title={( <ToolTipEx
<React.Fragment> valueTooltip={valueTooltip}
<Typography variant="caption">{uiControl.name}</Typography> title={
<Divider /> (
<Typography variant="caption">{uiControl.comment}</Typography> <React.Fragment>
<Typography variant="caption">{uiControl.name}</Typography>
<Divider />
<Typography variant="caption">{uiControl.comment}</Typography>
</React.Fragment> </React.Fragment>
)} )}
> >
{children} <div>
{children}
</div>
</ToolTipEx> </ToolTipEx>
); );
} else { } else {
return ( return (
<ToolTipEx title={uiControl.name} <ToolTipEx valueTooltip={valueTooltip}
title={
(
<Typography variant="caption">{uiControl.name}</Typography>
)}
> >
{children} <div >
{children}
</div>
</ToolTipEx> </ToolTipEx>
); );
} }
+9 -7
View File
@@ -44,7 +44,6 @@ import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
import DialogActions from '@mui/material/DialogActions'; import DialogActions from '@mui/material/DialogActions';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent'; import DialogContent from '@mui/material/DialogContent';
import IconButton from '@mui/material/IconButton';
import IconButtonEx from './IconButtonEx'; import IconButtonEx from './IconButtonEx';
import OldDeleteIcon from './OldDeleteIcon'; import OldDeleteIcon from './OldDeleteIcon';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
@@ -995,7 +994,8 @@ export default withStyles(
}} > }} >
{this.state.reordering && ( {this.state.reordering && (
<Toolbar style={{ padding: 0 }}> <Toolbar style={{ padding: 0 }}>
<IconButton <IconButtonEx
tooltip="Close"
edge="start" edge="start"
color="inherit" color="inherit"
aria-label="cancel" aria-label="cancel"
@@ -1004,7 +1004,7 @@ export default withStyles(
} }
> >
<CloseIcon style={{ width: 24, height: 24 }} /> <CloseIcon style={{ width: 24, height: 24 }} />
</IconButton> </IconButtonEx>
<Typography noWrap component="div" sx={{ flexGrow: 1 }}> <Typography noWrap component="div" sx={{ flexGrow: 1 }}>
Reorder files Reorder files
</Typography> </Typography>
@@ -1013,7 +1013,8 @@ export default withStyles(
)} )}
{this.state.multiSelect && ( {this.state.multiSelect && (
<Toolbar style={{ padding: 0 }}> <Toolbar style={{ padding: 0 }}>
<IconButton <IconButtonEx
tooltip="Close"
edge="start" edge="start"
color="inherit" color="inherit"
aria-label="cancel" aria-label="cancel"
@@ -1023,7 +1024,7 @@ export default withStyles(
}} }}
> >
<CloseIcon style={{ width: 24, height: 24 }} /> <CloseIcon style={{ width: 24, height: 24 }} />
</IconButton> </IconButtonEx>
<Typography noWrap component="div" sx={{ flexGrow: 1 }}> <Typography noWrap component="div" sx={{ flexGrow: 1 }}>
{this.state.selectedFiles.length.toString() + " files selected."} {this.state.selectedFiles.length.toString() + " files selected."}
</Typography> </Typography>
@@ -1071,7 +1072,8 @@ export default withStyles(
{!(this.state.reordering || this.state.multiSelect) && ( {!(this.state.reordering || this.state.multiSelect) && (
<> <>
<Toolbar style={{ padding: 0 }}> <Toolbar style={{ padding: 0 }}>
<IconButton <IconButtonEx
tooltip="Back"
edge="start" edge="start"
color="inherit" color="inherit"
aria-label="back" aria-label="back"
@@ -1079,7 +1081,7 @@ export default withStyles(
onClick={() => { this.props.onCancel(); }} onClick={() => { this.props.onCancel(); }}
> >
<ArrowBackIcon style={{ width: 24, height: 24 }} /> <ArrowBackIcon style={{ width: 24, height: 24 }} />
</IconButton> </IconButtonEx>
<Typography noWrap component="div" sx={{ flexGrow: 1 }}> <Typography noWrap component="div" sx={{ flexGrow: 1 }}>
{this.props.fileProperty.label} {this.props.fileProperty.label}
</Typography> </Typography>
+4 -3
View File
@@ -29,7 +29,7 @@ import TextField, { StandardTextFieldProps } from "@mui/material/TextField";
import DialogTitle from "@mui/material/DialogTitle"; import DialogTitle from "@mui/material/DialogTitle";
import DialogEx from "./DialogEx"; import DialogEx from "./DialogEx";
import Toolbar from "@mui/material/Toolbar"; import Toolbar from "@mui/material/Toolbar";
import IconButton from "@mui/material/IconButton"; import IconButtonEx from "./IconButtonEx";
import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Slider, { SliderProps } from "@mui/material/Slider"; import Slider, { SliderProps } from "@mui/material/Slider";
@@ -456,7 +456,8 @@ export default function LoopDialog(props: LoopDialogProps) {
> >
<DialogTitle style={{ paddingTop: 0 }}> <DialogTitle style={{ paddingTop: 0 }}>
<Toolbar style={{ padding: 0 }}> <Toolbar style={{ padding: 0 }}>
<IconButton <IconButtonEx
tooltip="Back"
edge="start" edge="start"
color="inherit" color="inherit"
aria-label="back" aria-label="back"
@@ -467,7 +468,7 @@ export default function LoopDialog(props: LoopDialogProps) {
}} }}
> >
<ArrowBackIcon style={{ width: 24, height: 24 }} /> <ArrowBackIcon style={{ width: 24, height: 24 }} />
</IconButton> </IconButtonEx>
<Typography noWrap component="div" sx={{ flexGrow: 1 }}> <Typography noWrap component="div" sx={{ flexGrow: 1 }}>
Loop Loop
</Typography> </Typography>
+8 -5
View File
@@ -33,7 +33,8 @@ import Utility from './Utility';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined'; import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined';
import MicOutlinedIcon from '@mui/icons-material/MicOutlined'; import MicOutlinedIcon from '@mui/icons-material/MicOutlined';
import IconButton from '@mui/material/IconButton'; import IconButtonEx from './IconButtonEx';
import NumericInput from './NumericInput'; import NumericInput from './NumericInput';
@@ -259,7 +260,8 @@ const MidiBindingView =
this.generateMidiNoteSelects() this.generateMidiNoteSelects()
} }
</Select> </Select>
<IconButton <IconButtonEx
tooltip="Listen for MIDI input"
onClick={() => { onClick={() => {
if (this.props.listen) { if (this.props.listen) {
this.props.onListen(-2, "", false) this.props.onListen(-2, "", false)
@@ -273,7 +275,7 @@ const MidiBindingView =
) : ( ) : (
<MicNoneOutlinedIcon /> <MicNoneOutlinedIcon />
)} )}
</IconButton> </IconButtonEx>
</div> </div>
) )
} }
@@ -292,7 +294,8 @@ const MidiBindingView =
this.generateControlSelects() this.generateControlSelects()
} }
</Select> </Select>
<IconButton <IconButtonEx
tooltip="Listen for MIDI input"
onClick={() => { onClick={() => {
if (this.props.listen) { if (this.props.listen) {
this.props.onListen(-2, "", false) this.props.onListen(-2, "", false)
@@ -306,7 +309,7 @@ const MidiBindingView =
) : ( ) : (
<MicNoneOutlinedIcon /> <MicNoneOutlinedIcon />
)} )}
</IconButton> </IconButtonEx>
</div> </div>
) )
+4 -3
View File
@@ -30,7 +30,7 @@ import { withStyles } from "tss-react/mui";
import AppBar from '@mui/material/AppBar'; import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import IconButton from '@mui/material/IconButton'; import IconButtonEx from './IconButtonEx';
import MidiBinding from './MidiBinding'; import MidiBinding from './MidiBinding';
import MidiBindingView from './MidiBindingView'; import MidiBindingView from './MidiBindingView';
import Snackbar from '@mui/material/Snackbar'; import Snackbar from '@mui/material/Snackbar';
@@ -350,14 +350,15 @@ export const MidiBindingDialog =
<div style={{ flex: "0 0 auto" }}> <div style={{ flex: "0 0 auto" }}>
<AppBar className={classes.dialogAppBar} > <AppBar className={classes.dialogAppBar} >
<Toolbar> <Toolbar>
<IconButton <IconButtonEx
tooltip="Back"
edge="start" edge="start"
color="inherit" color="inherit"
onClick={this.handleClose} onClick={this.handleClose}
aria-label="back" aria-label="back"
size="large"> size="large">
<ArrowBackIcon /> <ArrowBackIcon />
</IconButton> </IconButtonEx>
<Typography noWrap variant="h6" className={classes.dialogTitle}> <Typography noWrap variant="h6" className={classes.dialogTitle}>
Preset MIDI Bindings Preset MIDI Bindings
</Typography> </Typography>
@@ -27,8 +27,7 @@ import DialogActions from '@mui/material/DialogActions';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import FormControlLabel from '@mui/material/FormControlLabel'; import FormControlLabel from '@mui/material/FormControlLabel';
import ChannelBindingHelpDialog from './ChannelBindingsHelpDialog'; import ChannelBindingHelpDialog from './ChannelBindingsHelpDialog';
import IconButtonEx from './IconButtonEx';
import IconButton from '@mui/material/IconButton';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import Checkbox from '@mui/material/Checkbox'; import Checkbox from '@mui/material/Checkbox';
@@ -167,7 +166,9 @@ function MidiChannelBindingDialog(props: MidiChannelBindingDialogProps) {
} label="Allow Program Changes" } label="Allow Program Changes"
/> />
{false&&( // wait until the implementation stabilizes before exposing the help dialog. {false&&( // wait until the implementation stabilizes before exposing the help dialog.
<IconButton <IconButtonEx
tooltip="Help"
aria-label="help"
onClick={() => { setHelpDialog(true); }} onClick={() => { setHelpDialog(true); }}
size="large"> size="large">
<InfoOutlinedIcon color='inherit' <InfoOutlinedIcon color='inherit'
@@ -176,7 +177,7 @@ function MidiChannelBindingDialog(props: MidiChannelBindingDialogProps) {
opacity: 0.6 opacity: 0.6
}} }}
/> />
</IconButton> </IconButtonEx>
)} )}
</div> </div>
+21 -6
View File
@@ -146,6 +146,7 @@ export interface PluginControlProps extends WithStyles<typeof pluginControlStyle
type PluginControlState = { type PluginControlState = {
error: boolean; error: boolean;
editFocused: boolean; editFocused: boolean;
previewValue?: string;
}; };
const PluginControl = const PluginControl =
@@ -167,7 +168,9 @@ const PluginControl =
this.state = { this.state = {
error: false, error: false,
editFocused: false editFocused: false,
previewValue: undefined
}; };
this.model = PiPedalModelFactory.getInstance(); this.model = PiPedalModelFactory.getInstance();
this.imgRef = React.createRef(); this.imgRef = React.createRef();
@@ -406,6 +409,7 @@ const PluginControl =
img.style.opacity = "" + SELECTED_OPACITY; img.style.opacity = "" + SELECTED_OPACITY;
} }
} }
this.setState({ previewValue: undefined });
} else { } else {
if (this.isExtraTouch(e)) { if (this.isExtraTouch(e)) {
@@ -494,7 +498,9 @@ const PluginControl =
if (this.pointersDown !== 0) { if (this.pointersDown !== 0) {
--this.pointersDown; --this.pointersDown;
} }
if (this.pointersDown === 0) {
this.setState({ previewValue: undefined });
}
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -543,7 +549,7 @@ const PluginControl =
} }
clickSlop() { clickSlop() {
return 3.5; // maybe larger on touch devices. return 5; // maybe larger on touch devices.
} }
onPointerMove(e: PointerEvent<SVGSVGElement>): void { onPointerMove(e: PointerEvent<SVGSVGElement>): void {
if (this.isCapturedPointer(e)) { if (this.isCapturedPointer(e)) {
@@ -604,6 +610,7 @@ const PluginControl =
break; break;
} }
} }
this.setState({ previewValue: undefined});
} }
previewInputValue(value: number, commitValue: boolean) { previewInputValue(value: number, commitValue: boolean) {
let range = this.valueToRange(value); let range = this.valueToRange(value);
@@ -667,6 +674,12 @@ const PluginControl =
if (displayValue) { if (displayValue) {
let v = this.formatDisplayValue(this.props.uiControl, value); let v = this.formatDisplayValue(this.props.uiControl, value);
displayValue.childNodes[0].textContent = v; displayValue.childNodes[0].textContent = v;
if (commitValue) {
this.setState({ previewValue: undefined });
} else {
this.setState({ previewValue: v });
}
} }
let selectElement = this.selectRef.current; let selectElement = this.selectRef.current;
if (selectElement) { if (selectElement) {
@@ -1026,7 +1039,8 @@ const PluginControl =
) )
: (isGraphicEq) ? ( : (isGraphicEq) ? (
<div style={{ flex: "0 1 auto" }}> <div style={{ flex: "0 1 auto" }}>
<ControlTooltip uiControl={control}>
<ControlTooltip uiControl={control} valueTooltip={this.state.previewValue}>
<GraphicEqCtl <GraphicEqCtl
imgRef={this.imgRef} imgRef={this.imgRef}
position={this.getEqPosition()} position={this.getEqPosition()}
@@ -1045,7 +1059,8 @@ const PluginControl =
</div> </div>
) : ( ) : (
<div style={{ flex: "0 1 auto" }}> <div style={{ flex: "0 1 auto" }}>
<ControlTooltip uiControl={control}> <ControlTooltip uiControl={control}
valueTooltip={this.state.previewValue}>
<DialIcon ref={this.imgRef} <DialIcon ref={this.imgRef}
style={{ style={{
overscrollBehavior: "none", touchAction: "none", fill: dialColor, overscrollBehavior: "none", touchAction: "none", fill: dialColor,
@@ -1053,7 +1068,7 @@ const PluginControl =
}} }}
onTouchStart={this.onTouchStart} onTouchMove={this.onTouchMove} onTouchStart={this.onTouchStart} onTouchMove={this.onTouchMove}
onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp}
onPointerMoveCapture={this.onPointerMove} onPointerMove={this.onPointerMove}
onDrag={this.onDrag} onDrag={this.onDrag}
/> />
+6 -5
View File
@@ -18,7 +18,7 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import { SyntheticEvent, Component } from 'react'; import { SyntheticEvent, Component } from 'react';
import IconButton from '@mui/material/IconButton'; import IconButtonEx from './IconButtonEx';
import { PiPedalModel, PiPedalModelFactory, PresetIndex } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory, PresetIndex } from './PiPedalModel';
import SaveIconOutline from '@mui/icons-material/Save'; import SaveIconOutline from '@mui/icons-material/Save';
import MoreVertIcon from '@mui/icons-material/MoreVert'; import MoreVertIcon from '@mui/icons-material/MoreVert';
@@ -300,12 +300,12 @@ const PresetSelector =
justifyContent: "left", flexWrap: "nowrap", alignItems: "center", height: "100%", position: "relative" justifyContent: "left", flexWrap: "nowrap", alignItems: "center", height: "100%", position: "relative"
}}> }}>
<div style={{ flex: "0 0 auto" }}> <div style={{ flex: "0 0 auto" }}>
<IconButton <IconButtonEx tooltip="Save current preset"
style={{ flex: "0 0 auto", opacity: this.state.presets.presetChanged ? 1.0 : 0.0, color: "#FFFFFF" }} style={{ flex: "0 0 auto", opacity: this.state.presets.presetChanged ? 1.0 : 0.0, color: "#FFFFFF" }}
onClick={(e) => { this.handleSave(); }} onClick={(e) => { this.handleSave(); }}
size="large"> size="large">
<SaveIconOutline style={{ opacity: 0.75 }} color="inherit" /> <SaveIconOutline style={{ opacity: 0.75 }} color="inherit" />
</IconButton> </IconButtonEx>
</div> </div>
<div style={{ flex: "1 1 auto", minWidth: 60, maxWidth: 300, position: "relative", paddingRight: 12 }} > <div style={{ flex: "1 1 auto", minWidth: 60, maxWidth: 300, position: "relative", paddingRight: 12 }} >
@@ -337,13 +337,14 @@ const PresetSelector =
</Select> </Select>
</div> </div>
<div style={{ flex: "0 0 auto"}}> <div style={{ flex: "0 0 auto"}}>
<IconButton <IconButtonEx
tooltip="More..."
style={{ flex: "0 0 auto", color: "#FFFFFF" }} style={{ flex: "0 0 auto", color: "#FFFFFF" }}
onClick={(e) => this.handlePresetMenuClick(e)} onClick={(e) => this.handlePresetMenuClick(e)}
size="large" size="large"
> >
<MoreVertIcon style={{ opacity: 0.75 }} color="inherit" /> <MoreVertIcon style={{ opacity: 0.75 }} color="inherit" />
</IconButton> </IconButtonEx>
<Menu <Menu
id="edit-presets-menu" id="edit-presets-menu"
anchorEl={this.state.presetsMenuAnchorRef} anchorEl={this.state.presetsMenuAnchorRef}
@@ -24,6 +24,7 @@ import ListItemButton from '@mui/material/ListItemButton';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions'; import DialogActions from '@mui/material/DialogActions';
import FormControlLabel from '@mui/material/FormControlLabel'; import FormControlLabel from '@mui/material/FormControlLabel';
import Typography from '@mui/material/Typography';
import Checkbox from '@mui/material/Checkbox'; import Checkbox from '@mui/material/Checkbox';
import {AlsaMidiDeviceInfo} from './AlsaMidiDeviceInfo'; import {AlsaMidiDeviceInfo} from './AlsaMidiDeviceInfo';
@@ -110,6 +111,10 @@ function SelectMidiChannelsDialog(props: SelectMidiChannelsDialogProps) {
) )
)} )}
{availableChannels.length === 0 && (
<Typography variant="body2" style={{ marginLeft: 32, marginRight: 24,marginTop:8, marginBottom: 16}}>
No MIDI devices found.</Typography>
)}
</List> </List>
<DialogActions> <DialogActions>
<Button onClick={handleClose} variant="dialogSecondary" > <Button onClick={handleClose} variant="dialogSecondary" >
+10 -7
View File
@@ -27,7 +27,7 @@ import CssBaseline from '@mui/material/CssBaseline';
import {createStyles} from './WithStyles'; import {createStyles} from './WithStyles';
import { withStyles } from "tss-react/mui"; import { withStyles } from "tss-react/mui";
import IconButton from '@mui/material/IconButton'; import IconButtonEx from './IconButtonEx';
import FullscreenIcon from '@mui/icons-material/Fullscreen'; import FullscreenIcon from '@mui/icons-material/Fullscreen';
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit'; import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import ResizeResponsiveComponent from './ResizeResponsiveComponent';
@@ -271,14 +271,15 @@ const SnapshotEditor = withStyles(
<CssBaseline /> <CssBaseline />
<AppBar position="static" sx={{ bgcolor: isDarkMode() ? getBackgroundColor("purple") : "#200040" }} > <AppBar position="static" sx={{ bgcolor: isDarkMode() ? getBackgroundColor("purple") : "#200040" }} >
<Toolbar variant="dense" > <Toolbar variant="dense" >
<IconButton <IconButtonEx
tooltip="Back"
edge="start" edge="start"
aria-label="menu" aria-label="menu"
color="inherit" color="inherit"
onClick={() => { this.handleOk(); }} onClick={() => { this.handleOk(); }}
size="large"> size="large">
<ArrowBackIcon /> <ArrowBackIcon />
</IconButton> </IconButtonEx>
{!this.state.collapseLabel && ( {!this.state.collapseLabel && (
<Typography style={{ flex: "0 1 auto", opacity: 0.66 }} <Typography style={{ flex: "0 1 auto", opacity: 0.66 }}
variant="body1" noWrap variant="body1" noWrap
@@ -310,7 +311,8 @@ const SnapshotEditor = withStyles(
this.setState({ color: newColor }); this.setState({ color: newColor });
}} /> }} />
{this.state.canFullScreen && {this.state.canFullScreen &&
<IconButton <IconButtonEx
tooltip={this.state.isFullScreen ? "Exit Full Screen" : "Full Screen"}
aria-label="full-screen" aria-label="full-screen"
onClick={() => { this.toggleFullScreen(); }} onClick={() => { this.toggleFullScreen(); }}
color="inherit" color="inherit"
@@ -322,15 +324,16 @@ const SnapshotEditor = withStyles(
)} )}
</IconButton> </IconButtonEx>
} }
<IconButton <IconButtonEx
tooltip="Cancel"
aria-label="cancel" aria-label="cancel"
onClick={() => { this.props.onClose(); }} onClick={() => { this.props.onClose(); }}
color="inherit" color="inherit"
size="medium"> size="medium">
<CloseIcon /> <CloseIcon />
</IconButton> </IconButtonEx>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
+4 -3
View File
@@ -36,7 +36,7 @@ import MidiBinding from './MidiBinding';
import Utility from './Utility'; import Utility from './Utility';
import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined'; import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined';
import MicOutlinedIcon from '@mui/icons-material/MicOutlined'; import MicOutlinedIcon from '@mui/icons-material/MicOutlined';
import IconButton from '@mui/material/IconButton'; import IconButtonEx from './IconButtonEx';
@@ -195,7 +195,8 @@ const SystemMidiBindingView =
</div> </div>
) )
} }
<IconButton <IconButtonEx
tooltip="Listen for MIDI input"
onClick={() => { onClick={() => {
if (this.props.listen) { if (this.props.listen) {
this.props.onListen(-2, "", false) this.props.onListen(-2, "", false)
@@ -209,7 +210,7 @@ const SystemMidiBindingView =
) : ( ) : (
<MicNoneOutlinedIcon /> <MicNoneOutlinedIcon />
)} )}
</IconButton> </IconButtonEx>
</div> </div>
); );
+15 -16
View File
@@ -31,13 +31,13 @@ import { PiPedalModel, PiPedalModelFactory, ListenHandle } from './PiPedalModel'
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { Theme } from '@mui/material/styles'; import { Theme } from '@mui/material/styles';
import WithStyles from './WithStyles'; import WithStyles from './WithStyles';
import {createStyles} from './WithStyles'; import { createStyles } from './WithStyles';
import { withStyles } from "tss-react/mui"; import { withStyles } from "tss-react/mui";
import AppBar from '@mui/material/AppBar'; import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import IconButton from '@mui/material/IconButton'; import IconButtonEx from './IconButtonEx';
import MidiBinding from './MidiBinding'; import MidiBinding from './MidiBinding';
import SystemMidiBindingView from './SystemMidiBindingView'; import SystemMidiBindingView from './SystemMidiBindingView';
import Snackbar from '@mui/material/Snackbar'; import Snackbar from '@mui/material/Snackbar';
@@ -69,11 +69,11 @@ const styles = (theme: Theme) => createStyles({
paddingTop: 12 paddingTop: 12
}), }),
plainRow: css({ plainRow: css({
borderWidth: "1px 0px 0px 0px", borderStyle: "solid", borderColor: "transparent" borderWidth: "1px 0px 0px 0px", borderStyle: "solid", borderColor: "transparent"
}), }),
dividerRow: css({ dividerRow: css({
borderWidth: "1px 0px 0px 0px", borderStyle: "solid", borderColor: theme.palette.divider borderWidth: "1px 0px 0px 0px", borderStyle: "solid", borderColor: theme.palette.divider
}) })
}); });
@@ -139,7 +139,7 @@ export const SystemMidiBindingDialog =
} }
else if (item.symbol === "nextProgram") { else if (item.symbol === "nextProgram") {
displayName = "Next Preset"; displayName = "Next Preset";
}else if (item.symbol === "prevProgram") { } else if (item.symbol === "prevProgram") {
displayName = "Previous Preset"; displayName = "Previous Preset";
} }
else if (item.symbol === "snapshot1") { else if (item.symbol === "snapshot1") {
@@ -170,8 +170,7 @@ export const SystemMidiBindingDialog =
} else { } else {
found = false; found = false;
} }
if (found) if (found) {
{
result.push(new BindingEntry(displayName, listenInstanceId, item)); result.push(new BindingEntry(displayName, listenInstanceId, item));
++listenInstanceId; ++listenInstanceId;
} }
@@ -277,12 +276,11 @@ export const SystemMidiBindingDialog =
for (var item of items) { for (var item of items) {
let symbol = item.midiBinding.symbol; let symbol = item.midiBinding.symbol;
let hasDivider = symbol === "snapshot1" || symbol === "stopHotspot" || symbol === "shotdown"; let hasDivider = symbol === "snapshot1" || symbol === "stopHotspot" || symbol === "shotdown";
if (hasDivider) if (hasDivider) {
{
result.push( result.push(
<tr> <tr>
<td colSpan={2} className={classes.dividerRow}><div style={{height: 1}} /></td> <td colSpan={2} className={classes.dividerRow}><div style={{ height: 1 }} /></td>
</tr> </tr>
); );
} }
@@ -321,7 +319,7 @@ export const SystemMidiBindingDialog =
render() { render() {
let props = this.props; let props = this.props;
let { open} = props; let { open } = props;
const classes = withStyles.getClasses(this.props); const classes = withStyles.getClasses(this.props);
if (!open) { if (!open) {
@@ -332,20 +330,21 @@ export const SystemMidiBindingDialog =
<DialogEx tag="systemMidiBindings" open={open} fullWidth onClose={this.handleClose} aria-labelledby="Rename-dialog-title" <DialogEx tag="systemMidiBindings" open={open} fullWidth onClose={this.handleClose} aria-labelledby="Rename-dialog-title"
fullScreen={true} fullScreen={true}
style={{ userSelect: "none" }} style={{ userSelect: "none" }}
onEnterKey={()=>{}} onEnterKey={() => { }}
> >
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}> <div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
<div style={{ flex: "0 0 auto" }}> <div style={{ flex: "0 0 auto" }}>
<AppBar className={classes.dialogAppBar} > <AppBar className={classes.dialogAppBar} >
<Toolbar> <Toolbar>
<IconButton <IconButtonEx
tooltip="Back"
edge="start" edge="start"
color="inherit" color="inherit"
onClick={this.handleClose} onClick={this.handleClose}
aria-label="back" aria-label="back"
size="large"> size="large">
<ArrowBackIcon /> <ArrowBackIcon />
</IconButton> </IconButtonEx>
<Typography variant="h6" className={classes.dialogTitle}> <Typography variant="h6" className={classes.dialogTitle}>
System MIDI Bindings System MIDI Bindings
</Typography> </Typography>
+4 -3
View File
@@ -23,7 +23,7 @@ import { withStyles } from "tss-react/mui";
import {createStyles} from './WithStyles'; import {createStyles} from './WithStyles';
import IconButton from '@mui/material/Toolbar'; import IconButtonEx from './IconButtonEx';
import Drawer from '@mui/material/Drawer'; import Drawer from '@mui/material/Drawer';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { Theme } from '@mui/material/styles'; import { Theme } from '@mui/material/styles';
@@ -100,9 +100,10 @@ export const TemporaryDrawer = withStyles(
> >
<div style={{ display: "flex", flexFlow: "row nowrap", justifyContent: "flex-start", alignItems: "center", width: "100%"}}> <div style={{ display: "flex", flexFlow: "row nowrap", justifyContent: "flex-start", alignItems: "center", width: "100%"}}>
<IconButton style={{ flex: "0 0 auto" }} > <IconButtonEx tooltip="Back"
style={{ flex: "0 0 auto" }} >
<ArrowBackIcon style={{ fill: '#666' }} /> <ArrowBackIcon style={{ fill: '#666' }} />
</IconButton> </IconButtonEx>
<img src="img/Pi-Logo-3.png" alt="" style={{height: 36}} /> <img src="img/Pi-Logo-3.png" alt="" style={{height: 36}} />
</div> </div>
{this.props.children} {this.props.children}
+4 -3
View File
@@ -32,7 +32,7 @@ import TextField, { StandardTextFieldProps } from '@mui/material/TextField';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton'; import IconButtonEx from './IconButtonEx';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import Timebase, { TimebaseUnits } from './Timebase'; import Timebase, { TimebaseUnits } from './Timebase';
@@ -198,7 +198,8 @@ export default function TimebaseSelectorDialog(props: TimebaseSelectorDialogProp
> >
<DialogTitle style={{ paddingTop: 0 }}> <DialogTitle style={{ paddingTop: 0 }}>
<Toolbar style={{ padding: 0 }}> <Toolbar style={{ padding: 0 }}>
<IconButton <IconButtonEx
tooltip="Back"
edge="start" edge="start"
color="inherit" color="inherit"
aria-label="back" aria-label="back"
@@ -206,7 +207,7 @@ export default function TimebaseSelectorDialog(props: TimebaseSelectorDialogProp
onClick={() => { handleClose(); }} onClick={() => { handleClose(); }}
> >
<ArrowBackIcon style={{ width: 24, height: 24 }} /> <ArrowBackIcon style={{ width: 24, height: 24 }} />
</IconButton> </IconButtonEx>
<Typography noWrap component="div" sx={{ flexGrow: 1 }}> <Typography noWrap component="div" sx={{ flexGrow: 1 }}>
Timebase Settings Timebase Settings
</Typography> </Typography>
+45 -51
View File
@@ -27,13 +27,12 @@ import LoopDialog from './LoopDialog';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import ButtonEx from './ButtonEx'; import ButtonEx from './ButtonEx';
import IconButton from '@mui/material/IconButton'; import IconButtonEx from './IconButtonEx';
import Pause from '@mui/icons-material/Pause'; import Pause from '@mui/icons-material/Pause';
import PlayArrow from '@mui/icons-material/PlayArrow'; import PlayArrow from '@mui/icons-material/PlayArrow';
import FastForward from '@mui/icons-material/FastForward'; import FastForward from '@mui/icons-material/FastForward';
import FastRewind from '@mui/icons-material/FastRewind'; import FastRewind from '@mui/icons-material/FastRewind';
import { PiPedalModelFactory, State } from './PiPedalModel'; import { PiPedalModelFactory, State } from './PiPedalModel';
import ButtonTooltip from './ButtonTooltip';
import { pathFileNameOnly } from './FileUtils'; import { pathFileNameOnly } from './FileUtils';
import ButtonBase from '@mui/material/ButtonBase'; import ButtonBase from '@mui/material/ButtonBase';
import FilePropertyDialog from './FilePropertyDialog'; import FilePropertyDialog from './FilePropertyDialog';
@@ -351,61 +350,56 @@ export default function ToobPlayerControl(
}, },
})} })}
> >
<ButtonTooltip title="Previous track"> <IconButtonEx tooltip="Previous track"
<IconButton style={{ opacity: (pluginState === PluginState.Idle ? 0.2 : 0.7) }}
style={{ opacity: (pluginState === PluginState.Idle ? 0.2 : 0.7) }} onClick={() => {
onClick={() => { if (position > start + 3.0) {
if (position > start + 3.0) { model.sendPedalboardControlTrigger(
props.instanceId,
"stop",
1
);
} else {
onPreviousTrack();
}
}}
>
<FastRewind fontSize="large" />
</IconButtonEx>
<IconButtonEx
tooltip={paused ? "Play" : "Pause"}
style={{ opacity: (pluginState === PluginState.Idle ? 0.2 : 0.7) }}
onClick={() => {
if (pluginState != PluginState.Idle) {
if (paused) {
model.sendPedalboardControlTrigger( model.sendPedalboardControlTrigger(
props.instanceId, props.instanceId,
"stop", "play",
1 1
); );
} else { } else {
onPreviousTrack(); model.sendPedalboardControlTrigger(
props.instanceId,
"pause",
1
);
} }
}} }
> }}
<FastRewind fontSize="large" /> >
</IconButton> {paused ? (
</ButtonTooltip> <PlayArrow sx={{ fontSize: '3rem' }} />
<ButtonTooltip title="Play/Pause"> ) : (
<IconButton <Pause sx={{ fontSize: '3rem' }} />
)}
style={{ opacity: (pluginState === PluginState.Idle ? 0.2 : 0.7) }} </IconButtonEx>
onClick={() => { <IconButtonEx tooltip="Next track"
if (pluginState != PluginState.Idle) { style={{ opacity: (pluginState == PluginState.Idle ? 0.2 : 0.7) }}
if (paused) { onClick={() => { onNextTrack(); }}
model.sendPedalboardControlTrigger( >
props.instanceId, <FastForward fontSize="large" />
"play", </IconButtonEx>
1
);
} else {
model.sendPedalboardControlTrigger(
props.instanceId,
"pause",
1
);
}
}
}}
>
{paused ? (
<PlayArrow sx={{ fontSize: '3rem' }} />
) : (
<Pause sx={{ fontSize: '3rem' }} />
)}
</IconButton>
</ButtonTooltip>
<ButtonTooltip title="Next track">
<IconButton
style={{ opacity: (pluginState == PluginState.Idle ? 0.2 : 0.7) }}
onClick={() => { onNextTrack(); }}
>
<FastForward fontSize="large" />
</IconButton>
</ButtonTooltip>
</Box> </Box>
); );
+189 -15
View File
@@ -25,39 +25,213 @@ import React from 'react';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
export interface ToolTipExProps extends React.ComponentProps<typeof Tooltip> { export interface ToolTipExProps extends React.ComponentProps<typeof Tooltip> {
valueTooltip?: React.ReactNode; // For use with value prop, to show a tooltip on the value.
} }
/* ToolTipEx: a reimplementation of the MUI Tooltip that works with pointer events and long presses. */
function ToolTipEx(props: ToolTipExProps) { function ToolTipEx(props: ToolTipExProps) {
let [disableToolTip, setDisableToolTip] = React.useState(false); let {title, valueTooltip, ...extras} = props;
let {title, ...extras} = props; let [open,setOpen]= React.useState(false);
function handleMouseDownCapture(event: React.PointerEvent<HTMLDivElement>) { let [isLongPress, setIsLongPress] = React.useState(false);
if ((event as any).pointerType === "mouse") { let [longPressLeaving, setLongPressLeaving] = React.useState(false);
setDisableToolTip(true); let [timeout, setTimeout] = React.useState<number>(0);
let [timeoutInstance, setTimeoutInstance] = React.useState<number>(0);
let [pointerDownPoint, setPointerdownPoint] = React.useState<{x: number, y: number} | null>(null);
const hoverTimeout = 1250;
const longpressTimeout = 500;
const longpressLeaveTimeout = 1500;
function startTimeout(timeout: number) {
setTimeout(timeout);
setTimeoutInstance(timeoutInstance + 1); // make useeffect run.
}
function stopTimeout() {
if (timeout > 0) {
startTimeout(0);
}
}
function handleMouseEnter(event: React.MouseEvent<HTMLDivElement>) {
setOpen(false);
startTimeout(hoverTimeout);
}
function handleMouseLeave(event: React.MouseEvent<HTMLDivElement>) {
setOpen(false);
stopTimeout();
}
function handlePointerDownCapture(event: React.PointerEvent<HTMLDivElement>) {
let pointerType = (event as any).pointerType || "n/a";
setIsLongPress(false);
if (pointerType === "mouse") {
setOpen(false);
stopTimeout(); // Reset hover timeout for mouse
} else { // pen, or touch.
setTimeout(longpressTimeout);
setOpen(false);
setPointerdownPoint({x: event.clientX, y: event.clientY});
} }
} }
function handlePointerMoveCapture(event: React.PointerEvent<HTMLDivElement>) { React.useEffect(()=> {
if ((event as any).pointerType !== "mouse") { let t = timeout;
setDisableToolTip(false); let handle: number | null = null;
if (valueTooltip === undefined) // no timeout if there's a value tooltip
{
if (t > 0) {
console.log("ToolTipEx: starting timeout for ", t);
handle = window.setTimeout(() => {
setOpen(true);
},t);
}
} }
return () => {
if (handle !== null) {
console.log("ToolTipEx: clearing timeout for ", t);
window.clearTimeout(handle);
}
};
},[timeoutInstance]);
React.useEffect(()=> {
if (longPressLeaving) {
let handle: number | null = null;
handle = window.setTimeout(() => {
setIsLongPress(false);
setLongPressLeaving(false);
}, longpressLeaveTimeout);
return () => {
if (handle !== null) {
window.clearTimeout(handle);
}
};
} else {
return () => { };
}
},[longPressLeaving]);
function handlePointerCancel(event: React.PointerEvent<HTMLDivElement>) {
setOpen(false);
setIsLongPress(false);
stopTimeout();
setPointerdownPoint(null); // Reset the mouse down point
}
function handlePointerMoveCapture(event: React.PointerEvent<HTMLDivElement>) {
let pointerType = (event as any).pointerType || "n/a";
if (pointerType === "mouse") {
setOpen(false);
startTimeout(hoverTimeout); // Reset hover timeout for mouse
} else { // pen, or touch.
if (pointerDownPoint) {
const dx = event.clientX - pointerDownPoint.x;
const dy = event.clientY - pointerDownPoint.y;
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
// If the mouse has moved more than 5 pixels, re-enable the tooltip
setOpen(false);
setIsLongPress(false);
stopTimeout();
setPointerdownPoint(null); // Reset the mouse down point
}
}
}
}
function handleConextMenu(event: React.MouseEvent<HTMLDivElement>) {
event.preventDefault(); // Prevent the default context menu from appearing
event.stopPropagation(); // Prevent the default context menu from appearing
if (pointerDownPoint) // touch sequence. This is a long press.
{
setIsLongPress(true);
setOpen(true);
stopTimeout();
}
} // Reset hover timeout for context menu
function handlePointerUpCapture(event: React.PointerEvent<HTMLDivElement>) {
let pointerType = (event as any).pointerType || "n/a";
if (pointerType === "mouse") {
setOpen(false);
stopTimeout(); // Reset hover timeout for mouse
} else { // pen, or touch.
stopTimeout();
setOpen(false);
if (isLongPress) {
// If this is a long press, we want to leave the tooltip open for a while.
setLongPressLeaving(true);
}
}
}
let effectiveTitle: React.ReactNode | null = null;
let placement: "top-start" | "right" = "top-start";
if (valueTooltip !== undefined && !isLongPress) {
effectiveTitle = valueTooltip; // Show value tooltip if available
placement = "right";
}
else {
if (open || isLongPress) {
effectiveTitle = title; // Show regular title if no value tooltip
}
}
function handleClickCapture(event: React.MouseEvent<HTMLDivElement>) {
setLongPressLeaving(false);
setIsLongPress(false);
setOpen(false);
stopTimeout(); // Reset hover timeout on click
} }
return ( return (
<div <div
onClickCapture={(e) => {
console.log("ToolTipEx: onClickCapture");
handleClickCapture(e);
}}
onMouseEnter={(e)=> {
console.log("ToolTipEx: onMouseEnter");
handleMouseEnter(e);
}}
onMouseLeave={(e)=> {
console.log("ToolTipEx: onMouseLeave");
handleMouseLeave(e);
}}
onPointerCancelCapture={(e) => {
console.log("ToolTipEx: onPointerCancelCapture");
handlePointerCancel(e);
}}
onContextMenuCapture={(e) => {
console.log("ToolTipEx: onContextMenuCapture");
handleConextMenu(e);
return false;
}}
onPointerDownCapture={(e) => { onPointerDownCapture={(e) => {
handleMouseDownCapture(e); console.log("ToolTipEx: onPointerDownCapture");
handlePointerDownCapture(e);
}} }}
onPointerMoveCapture={(e) => { onPointerMoveCapture={(e) => {
console.log("ToolTipEx: onPointerMoveCapture");
handlePointerMoveCapture(e); handlePointerMoveCapture(e);
}} }}
onMouseLeave={() => { onPointerUpCapture={(e) => {
setDisableToolTip(false); console.log("ToolTipEx: onPointerUpCapture");
} handlePointerUpCapture(e);
} }}
> >
<Tooltip {...extras} <Tooltip {...extras}
title={disableToolTip? undefined: title} open={open || isLongPress || valueTooltip !== undefined}
placement="top-start" arrow enterDelay={1500} enterNextDelay={1500} disableInteractive={true}
disableTouchListener={true}
disableHoverListener={true}
disableFocusListener={true}
title={effectiveTitle}
placement={ placement}
arrow enterDelay={1500} enterNextDelay={1500}
slotProps={{
transition: {
timeout: 0 // Disable transition for the tooltip
}
}}
/> />
</div> </div>
) )
+4 -3
View File
@@ -23,7 +23,7 @@
*/ */
import React from 'react'; import React from 'react';
import IconButton from '@mui/material/IconButton'; import IconButtonEx from './IconButtonEx';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
@@ -305,14 +305,15 @@ const WifiDirectConfigDialog =
{this.state.landscapeLayout && ( {this.state.landscapeLayout && (
<DialogContent > <DialogContent >
<div style={{ display: "flex", gap: 16, alignItems: "left" }}> <div style={{ display: "flex", gap: 16, alignItems: "left" }}>
<IconButton <IconButtonEx
tooltip="Back"
edge="start" edge="start"
color="inherit" color="inherit"
onClick={() => { this.props.onClose(); }} onClick={() => { this.props.onClose(); }}
aria-label="back" aria-label="back"
size="large"> size="large">
<ArrowBackIcon htmlColor="#888" /> <ArrowBackIcon htmlColor="#888" />
</IconButton> </IconButtonEx>
<FormControlLabel <FormControlLabel
control={( control={(
<Switch <Switch