diff --git a/ivette/src/dome/main/dome.ts b/ivette/src/dome/main/dome.ts index 3ef9fdc5adb6869c28323bdf1a4ff60543b25221..f58edbeca4e0c4b3b79bb9d19d58d44a95d6c6cb 100644 --- a/ivette/src/dome/main/dome.ts +++ b/ivette/src/dome/main/dome.ts @@ -49,6 +49,7 @@ import { IpcMainEvent, shell, dialog, + nativeTheme, } from 'electron'; import installExtension, { REACT_DEVELOPER_TOOLS } from 'dome/devtools'; import SYS, * as System from 'dome/system'; @@ -84,6 +85,30 @@ export const { DEVEL } = System; /** System platform */ export const { platform } = System; +// -------------------------------------------------------------------------- +// --- Native Theme +// -------------------------------------------------------------------------- + +ipcMain.handle('dome.ipc.theme', () => { + return nativeTheme.shouldUseDarkColors ? 'dark' : 'light'; +}); + +nativeTheme.on('updated', () => { + broadcast('dome.theme.updated'); +}); + +function setNativeTheme(theme: string | undefined) { + switch (theme) { + case 'dark': + case 'light': + case 'system': + nativeTheme.themeSource = theme; + return; + default: + console.warn('[dome] unknown theme', theme); + } +} + // -------------------------------------------------------------------------- // --- Settings // -------------------------------------------------------------------------- @@ -207,7 +232,10 @@ function applyStorageSettings(event: IpcMainEvent, args: Patch[]) { } function applyGlobalSettings(event: IpcMainEvent, args: Patch[]) { - applyPatches(obtainGlobalSettings(), args); + const settings: Store = obtainGlobalSettings(); + applyPatches(settings, args); + const theme = settings['dome-color-theme']; + if (typeof (theme) === 'string') setNativeTheme(theme); BrowserWindow.getAllWindows().forEach((w: BrowserWindow) => { const contents = w.webContents; if (contents.id !== event.sender.id) { @@ -512,6 +540,7 @@ function showSettingsWindow() { function restoreDefaultSettings() { GlobalSettings = {}; + nativeTheme.themeSource = 'system'; if (DEVEL) saveGlobalSettings(); WindowHandles.forEach((handle) => { diff --git a/ivette/src/dome/renderer/controls/style.css b/ivette/src/dome/renderer/controls/style.css index a7c192a15d462a89ed9982b5c848ea6c5a0a3b0e..4d04c9be87916a9a47f21ab8d20718afa81865db 100644 --- a/ivette/src/dome/renderer/controls/style.css +++ b/ivette/src/dome/renderer/controls/style.css @@ -11,6 +11,7 @@ .dome-xLabel .dome-xIcon { margin-right: 4px ; + fill: var(--info-text-discrete); } .dome-xLabel.dome-text-descr { @@ -39,21 +40,19 @@ margin: 1px ; padding: 1px 6px 1px 6px ; height: 16px ; - fill: #dedede ; - color: #dedede ; + fill: var(--background-intense) ; + color: var(--background-intense) ; font-family: sans-serif ; font-size: smaller ; font-weight: bold ; border-radius: 8px ; - background: #777 ; + background: var(--info-text) ; } -.dome-window-inactive .dome-xBadge, -.dome-disabled .dome-xBadge -{ - background: #ccc ; - color: #eee ; - fill: #eee ; +.dome-disabled .dome-xBadge { + background: var(--background-profound) ; + color: var(--background-intense) ; + fill: var(--background-intense) ; } .dome-xBadge label { @@ -65,13 +64,12 @@ /* -------------------------------------------------------------------------- */ .dome-control-enabled { - color: #333 ; font-weight: normal ; } .dome-control-disabled { - color: #b0b0b0 ; - fill: #b0b0b0 ; + color: var(--disabled-text) ; + fill: var(--disabled-text) ; font-weight: lighter ; } @@ -84,9 +82,11 @@ } /* -------------------------------------------------------------------------- */ -/* --- Styling Buttons --- */ +/* --- Styling Buttons (may be factorized with xToolBar) --- */ /* -------------------------------------------------------------------------- */ +/* Layout */ + .dome-xButton { flex: 0 0 auto ; align-self: baseline ; @@ -111,81 +111,130 @@ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.06); } -.dome-xButton:disabled, -.dome-xButton:active:disabled, -.dome-window-inactive .dome-xButton -{ - fill: #b0b0b0 ; - border-color: #ccc ; - background-color: #f4f4f4 ; +/* Disabled */ + +.dome-xButton:disabled, .dome-xButton:active:disabled { + fill: var(--disabled-text); + color: var(--disabled-text); + border-color: var(--border-discrete) ; background-image: none; } -.dome-xButton:active { - background-color: #ddd; +/* Selected */ + +.dome-xButton-selected { + fill: var(--info-text); + color: var(--info-text); + border: 1px solid transparent ; + background-color: var(--selected-button-img); background-image: none; } -.dome-xButton-default { - border-color: #c2c0c2 ; - background-color: #f4f4f4 ; +.dome-xButton-selected:hover:not(:disabled) { + background-color: var(--selected-button-hover) ; + background-image: none ; } -.dome-xButton-default:hover:not(:disabled) { - background-color: #fff ; +/* Selected & Disabled */ + +.dome-xButton-selected:disabled { + fill: var(--info-text) ; + color: var(--info-text) ; + border: 1px solid var(--border-discrete) ; } -.dome-xButton-selected { - border-color: #c2c0c2 ; - background-color: #ccc ; +/* Default button behaviors */ + +.dome-xButton-default:not(:disabled) { + color: var(--text); + border-color: var(--border); + background-image: var(--default-button-img); } -.dome-xButton-selected:hover:not(:disabled) { - background-color: #fff ; +.dome-xButton-default:hover:not(:disabled) { + background-color: var(--default-button-hover); + background-image: none; } -.dome-xButton-primary { - border-color: #888; - background-image: linear-gradient(to bottom, #449bef 0%, #4990e2 100%); +.dome-xButton-default:active:not(:disabled) { + background-color: var(--default-button-active); + background-image: none; +} + +/* Primary button behaviors */ + +.dome-xButton-primary:not(:disabled) { + color: var(--text); + border-color: var(--border); + background-image: var(--primary-button-img); } .dome-xButton-primary:hover:not(:disabled) { - background-color: #00b6ff ; + background-color: var(--primary-button-hover); background-image: none ; } -.dome-xButton-positive { - border-color: #888; - background-image: linear-gradient(to bottom, #60f577 0%, #3efb5b 100%); +.dome-xButton-primary:active:not(:disabled) { + background-color: var(--primary-button-active); + background-image: none; +} + +/* Positive button behaviors */ + +.dome-xButton-positive:not(:disabled) { + color: var(--text); + border-color: var(--border); + background-image: var(--positive-button-img); } .dome-xButton-positive:hover:not(:disabled) { - background-color: #00ff00 ; + background-color: var(--positive-button-hover); background-image: none ; } -.dome-xButton-negative { - border-color: #888; - fill: #333; - color: #333 ; - background-image: linear-gradient(to bottom, #ff827d 0%, #fb3832 100%); +.dome-xButton-positive:active:not(:disabled) { + background-color: var(--positive-button-active); + background-image: none; +} + +/* Negative button behaviors */ + +.dome-xButton-negative:not(:disabled) { + color: var(--text); + border-color: var(--border); + background-image: var(--negative-button-img); } .dome-xButton-negative:hover:not(:disabled) { - background-color: #ff0000 ; + background-color: var(--negative-button-hover); background-image: none ; } -.dome-xButton-warning { - border-color: #888; - background-image: linear-gradient(to bottom, #ffd483 0%, #ffb426 100%); +.dome-xButton-negative:active:not(:disabled) { + background-color: var(--negative-button-active); + background-image: none; +} + +/* Warning button behaviors */ + +.dome-xButton-warning:not(:disabled) { + color: var(--text); + border-color: var(--border); + background-image: var(--warning-button-img); } .dome-xButton-warning:hover:not(:disabled) { - background-color: #ecdd09 ; + background-color: var(--warning-button-hover); background-image: none ; } +.dome-xButton-warning:active:not(:disabled) { + background-color: var(--warning-button-active); + background-image: none; +} + +/* Buttons' labels */ + .dome-xButton-label { position: relative ; padding: 2px ; @@ -209,16 +258,15 @@ .dome-xButton-lcd { text-align: right ; - color: #61611a ; - background-color: #b2b798 ; - border-color: #c2c0c2 ; + color: var(--lcd-button-color) ; + background-color: var(--lcd-button-background) ; + border-color: var(--border) ; } .dome-xButton-led { display: inline ; position: relative ; - bottom: -2px ; - border-color: #999 ; + border-color: var(--border) ; border-style: solid ; border-width: 1px ; border-radius: 50% ; @@ -233,51 +281,44 @@ } .dome-xButton-led-active { - background: radial-gradient( circle at center , #9fd2f9 , #81c9ff ); + background: var(--default-led-img); } .dome-xButton-led-positive { - background: radial-gradient( circle at center , #50c140, #279c16 ); + background: var(--positive-led-img); } .dome-xButton-led-negative { - background: radial-gradient( circle at center , #ff5b56 , #f75a55c7 ); + background: var(--negative-led-img); } .dome-xButton-led-warning { - background: radial-gradient( circle at center , #ffc749 , #ecd44f ); + background: var(--warning-led-img); } /* -------------------------------------------------------------------------- */ /* --- Styling Meters --- */ /* -------------------------------------------------------------------------- */ - -meter.dome-xMeter { - background: white; - border-radius: 5px; - box-shadow: - 0 0 4px 4px rgba(0,0,0,0.15) inset; - height: 16px; - width: 70px; -} - -meter.dome-xMeter::-webkit-meter-bar { - background: transparent; - border-radius: 5px; - height: 14px; +meter::-webkit-meter-bar { + height: 14px; + width: 70px; + border: 1px solid var(--border); + border-radius: 2px; + background: none; + box-shadow: 0px 3px 2px -3px var(--text) inset; } -meter.dome-xMeter::-webkit-meter-optimum-value { - background: linear-gradient(to bottom, #4a0 0%, #8f0 20%, #4a0 100%); +meter::-webkit-meter-optimum-value { + background: var(--meter-optimum); } -meter.dome-xMeter::-webkit-meter-suboptimum-value { - background: linear-gradient(to bottom, #aa0 0%, #ff0 20%, #aa0 100%); +meter::-webkit-meter-suboptimum-value { + background: var(--meter-subopti); } -meter.dome-xMeter::-webkit-meter-even-less-good-value { - background: linear-gradient(to bottom, #a40 0%, #f80 20%, #a40 100%); +meter::-webkit-meter-even-less-good-value { + background: var(--meter-evenless); } /* -------------------------------------------------------------------------- */ @@ -313,22 +354,25 @@ meter.dome-xMeter::-webkit-meter-even-less-good-value { background: transparent ; } -.dome-xIconButton:hover { fill: #000 ; background: #c0c0c0 ; } +.dome-xIconButton:hover { + fill: var(--text) ; + background: var(--background-profound-hover) ; +} -.dome-xIconButton-default { fill: #777 ; } +.dome-xIconButton-default { fill: var(--info-text) ; } -.dome-xIconButton-selected:not(:hover) { fill: #449bef ; } -.dome-xIconButton-selected:hover { fill: #c54dae ; } +.dome-xIconButton-selected:not(:hover) { fill: var(--activated-button-color) ; } +.dome-xIconButton-selected:hover { fill: var(--activated-button-hover) ; } -.dome-xIconButton-positive { fill: #279c16 ; } +.dome-xIconButton-positive { fill: var(--positive-button-color) ; } -.dome-xIconButton-negative { fill: #fb3832 ; } +.dome-xIconButton-negative { fill: var(--negative-button-color) ; } -.dome-xIconButton-warning { fill: orange ; } +.dome-xIconButton-warning { fill: var(--warning-button-color) ; } .dome-xIconButton.dome-control-disabled, .dome-xIconButton.dome-control-disabled:hover { - fill: #aeafae ; + fill: var(--disabled-text) ; background: inherit ; } diff --git a/ivette/src/dome/renderer/dark.css b/ivette/src/dome/renderer/dark.css new file mode 100644 index 0000000000000000000000000000000000000000..ee7b62fbc147e752a86bd4e70e32e0049a54db57 --- /dev/null +++ b/ivette/src/dome/renderer/dark.css @@ -0,0 +1,102 @@ +@media (prefers-color-scheme: dark) { + :root { + --text: #b4cacd; + --text-discrete: #879da0; + --disabled-text: #506679; + --info-text: #748a8d; + --info-text-discrete: #778094; + --text-highlighted: #d4eaed; + + --code-hover: #005137; + --code-select: #4f3d24; + --code-select-hover: #5f4d34; + --dead-code: #444; + --non-terminating: #444; + --highlighted-marker: #778822; + --code-bullet: #0a2234; + + --splitter: #708294; + --border: #031b2d; + --border-discrete: #2a4254; + + --background: #2c394b; + --background-profound: #082032; + --background-profound-hover: #0e2638; + --background-disabled: #525f71; + --background-disabled-hover: #4c596b; + --background-intense: #132b41; + --background-softer: #364455; + --background-sidebar: #183042; + --background-button-hover: #c0c0c0; + --background-alterning-odd: #354154; + --background-alterning-even: #475366; + --background-interaction: #4c596b; + --background-report: #1e2b3d; + + --selected-element: #082032; + --checked-element: #68758e; + + --lcd-button-color: #21211a; + --lcd-button-background: #727798; + + --default-button-color: #3c495b; + --default-led-img: radial-gradient(circle at center, #5f92b9, #4189bf); + --default-button-img: linear-gradient(to bottom, #3c495b 0%, #455164 100%); + --default-button-hover: #364355; + --default-button-active: #303d4f; + + --primary-button-img: linear-gradient(to bottom, #146bbf 0%, #1960b2 100%); + --primary-button-hover: #0e65b9; + --primary-button-active: #085fb3; + + --positive-button-color: #327024; + --positive-led-img: radial-gradient(circle at center, #20a110, #177c16); + --positive-button-img: linear-gradient(to bottom, #327024 0%, #2e6c20 100%); + --positive-button-hover: #28661a; + --positive-button-active: #226014; + + --negative-button-color: #bc150e; + --negative-led-img: radial-gradient(circle at center, #df3b36, #c72a25c7); + --negative-button-img: linear-gradient(to bottom, #bc150e 0%, #cf1c17 100%); + --negative-button-hover: #b60f08; + --negative-button-active: #b00903; + + --warning-button-color: #a4601a; + --warning-led-img: radial-gradient(circle at center, #efa739, #ccb41f); + --warning-button-img: linear-gradient(to bottom, #a4601a 0%, #a6571c 100%); + --warning-button-hover: #9e5114; + --warning-button-active: #984b0e; + + --cancel-button-img: #082032; + --cancel-button-hover: #0e2638; + --cancel-button-active: #041c2e; + + --selected-button-img: #355174; + --selected-button-hover: #456184; + + --activated-button-color: #348bdf; + --activated-button-hover: #b53d9e; + + --grid-layout-holder: #266475; + --grid-layout-target: #34b4c0; + + --error: darkorange; + --warning: darkorange; + + --meter-optimum: linear-gradient(to bottom, #170 0%, #5c0 20%, #170 100%); + --meter-subopti: linear-gradient(to bottom, #770 0%, #cc0 20%, #770 100%); + --meter-evenless: linear-gradient(to bottom, #710 0%, #c50 20%, #710 100%); + + --eva-state-before: #082032; + --eva-state-after: #321428; + --eva-state-then: #98a02e; + --eva-state-else: #8f030e; + --eva-probes-pinned: #1f493f; + --eva-probes-pinned-focused: #025d22; + --eva-probes-transient: #bfb095; + --eva-probes-transient-focused: #9f3c20; + --eva-alarms-true: green; + --eva-alarms-false: #fb4e4a; + --eva-alarms-unknown: darkorange; + } +} diff --git a/ivette/src/dome/renderer/dome.tsx b/ivette/src/dome/renderer/dome.tsx index 4442d4d2ca73040270f8599e06473555d3802b88..1770a29b7f2d5412424b59ab9fcdcd04940ed798 100644 --- a/ivette/src/dome/renderer/dome.tsx +++ b/ivette/src/dome/renderer/dome.tsx @@ -49,6 +49,8 @@ import { ipcRenderer } from 'electron'; import SYS, * as System from 'dome/system'; import * as Json from 'dome/data/json'; import * as Settings from 'dome/data/settings'; +import './dark.css'; +import './light.css'; import './style.css'; import { State } from './data/states'; diff --git a/ivette/src/dome/renderer/frame/style.css b/ivette/src/dome/renderer/frame/style.css index 4e20866b1726d61b58c0e684813743c6da07939e..69a6511a02a360c4fb9b3d259e716a37491e8f6c 100644 --- a/ivette/src/dome/renderer/frame/style.css +++ b/ivette/src/dome/renderer/frame/style.css @@ -38,24 +38,14 @@ border-right-style: solid ; } -.dome-window-inactive .dome-xTab.dome-inactive { - color: #606060 ; - border-color: inherit ; -} - .dome-xTab.dome-inactive:hover { - background: #bababa ; + background: var(--background-disabled-hover) ; } .dome-xTab.dome-inactive { - color: #606060 ; - border-color: #9c9c9c ; - background: #b4b4b4 ; -} - -.dome-window-inactive .dome-xTab.dome-inactive { - color: #b0b0b0 ; - background: #e6e6e6 ; + color: var(--text-discrete) ; + border-color: var(--border) ; + background: var(--background-disabled) ; } /* Closing Tab button */ @@ -82,16 +72,11 @@ left: initial ; } -.dome-active .dome-xTab-closing:hover { - background: #c0c0c0 ; -} - -.dome-inactive .dome-xTab-closing:hover { - background: #a0a0a0 ; +.dome-xTab-closing:hover { + background: var(--background-button-hover); } -.dome-xTab:hover .dome-xTab-closing , .dome-xTab:hover .dome-xTab-closing -{ +.dome-xTab:hover .dome-xTab-closing { opacity: 1 ; } @@ -107,10 +92,10 @@ padding: 0px ; } -.dome-window-active .dome-xSideBar.dome-color-frame, -.dome-window-active .dome-xSideBarSection-title.dome-color-frame +.dome-xSideBar.dome-color-frame, +.dome-xSideBarSection-title.dome-color-frame { - background: #e3e8ec ; + background: var(--background-sidebar) ; } /* -------------------------------------------------------------------------- */ @@ -148,7 +133,7 @@ .dome-xSideBarSection-hideshow { flex: 0 0 ; margin: 1px ; - color: #aaa ; + color: var(--info-text) ; width: 32px ; font-weight: normal ; display: inline ; @@ -177,12 +162,8 @@ scroll-margin-top: 30px; } -.dome-window-active .dome-xSideBarItem.dome-active { - background: #ccc ; -} - -.dome-window-inactive .dome-xSideBarItem.dome-active { - background: #ddd ; +.dome-xSideBarItem.dome-active { + background: var(--background-profound) ; } .dome-xSideBarItem > .dome-xLabel { @@ -193,8 +174,8 @@ } .dome-xSideBarItem.dome-disabled > .dome-xLabel { - color: #b0b0b0 ; - fill: #b0b0b0 ; + color: var(--disabled-text) ; + fill: var(--disabled-text) ; } .dome-xSideBarItem > .dome-xLabel:last-child { @@ -216,7 +197,7 @@ display: flex ; flex-direction: row ; flex-wrap: wrap ; - align-items: baseline ; + align-items: center ; border-width: 1px ; border-bottom-style: solid ; } @@ -258,12 +239,8 @@ height: 18px ; } -.dome-window-active .dome-xToolBar-vrule { - background: #aaa ; -} - -.dome-window-inactive .dome-xToolBar-vrule { - background: #ddd ; +.dome-xToolBar-vrule { + background: var(--splitter) ; } /* -------------------------------------------------------------------------- */ @@ -292,118 +269,115 @@ margin-left: 4px ; } -/* Background */ +/* Default button behaviors */ -.dome-window-active .dome-xToolBar-control { - background-image: linear-gradient(to bottom, #e8e8e8 0, #f1f1f1 100%); +.dome-xToolBar-control { + background-image: var(--default-button-img); + color: var(--text); } -.dome-window-active .dome-xToolBar-control:hover:not(:disabled) { - background-color: #ffffff ; +.dome-xToolBar-control:hover:not(:disabled) { + background-color: var(--default-button-hover); background-image: none ; } -.dome-window-active .dome-xToolBar-control.dome-xToolBar-positive:not(:disabled) { - background-image: linear-gradient(to bottom, #34ff52 0%, #48fd64 100%); +.dome-xToolBar-control:active:not(:disabled) { + background-color: var(--default-button-active); + background-image: none; } -.dome-window-active .dome-xToolBar-control.dome-xToolBar-positive:hover:not(:disabled) { - background-color: #00ff00 ; - background-image: none ; -} +/* Positive button behaviors */ -.dome-window-active .dome-xToolBar-control.dome-xToolBar-negative:not(:disabled) { - color: #ccc ; - fill: #ccc ; - background-image: linear-gradient(to bottom, #ec453e 0%, #ff4c47 100%); +.dome-xToolBar-control.dome-xToolBar-positive:not(:disabled) { + background-image: var(--positive-button-img); } -.dome-window-active .dome-xToolBar-control.dome-xToolBar-negative:hover:not(:disabled) { - background-color: red ; +.dome-xToolBar-control.dome-xToolBar-positive:hover:not(:disabled) { + background-color: var(--positive-button-hover); background-image: none ; } -.dome-window-active .dome-xToolBar-control.dome-xToolBar-warning:not(:disabled) { - background-image: linear-gradient(to bottom, #fece72 0%, #fcaa0e 100%); +.dome-xToolBar-control.dome-xToolBar-positive:active:not(:disabled) { + background-color: var(--positive-button-active); + background-image: none; } -.dome-window-active .dome-xToolBar-control.dome-xToolBar-warning:hover:not(:disabled) { - background-color: orange ; - background-image: none ; +/* Negative button behaviors */ + +.dome-xToolBar-control.dome-xToolBar-negative:not(:disabled) { + background-image: var(--negative-button-img); } -.dome-window-active .dome-xToolBar-control.dome-xToolBar-cancel:not(:disabled) { - background-color: #c2c0c2 ; +.dome-xToolBar-control.dome-xToolBar-negative:hover:not(:disabled) { + background-color: var(--negative-button-hover); background-image: none ; } -.dome-window-active .dome-xToolBar-control.dome-xToolBar-cancel:hover:not(:disabled) { - background-image: linear-gradient(to bottom, #e8e8e8 0, #f1f1f1 100%); +.dome-xToolBar-control.dome-xToolBar-negative:active:not(:disabled) { + background-color: var(--negative-button-active); + background-image: none; } -.dome-window-inactive .dome-xToolBar-control { - box-shadow: none ; +/* Warning button behaviors */ + +.dome-xToolBar-control.dome-xToolBar-warning:not(:disabled) { + background-image: var(--warning-button-img); +} + +.dome-xToolBar-control.dome-xToolBar-warning:hover:not(:disabled) { + background-color: var(--warning-button-hover); background-image: none ; } -/* Activated */ +.dome-xToolBar-control.dome-xToolBar-warning:active:not(:disabled) { + background-color: var(--warning-button-active); + background-image: none; +} + +/* Cancel button behaviors */ -.dome-window-active .dome-xToolBar-control:active:not(:disabled) { - fill: #ddd ; - color: #ddd ; - background-color: gray ; +.dome-xToolBar-control.dome-xToolBar-cancel:not(:disabled) { + background-color: var(--cancel-button-img); background-image: none ; } -/* Disabled */ +.dome-xToolBar-control.dome-xToolBar-cancel:hover:not(:disabled) { + background-image: var(--cancel-button-hover); +} -.dome-window-active .dome-xToolBar-control:disabled { - fill: #ccc ; - color: #ccc ; - box-shadow: none ; - border-color: #bbb ; +.dome-xToolBar-control.dome-xToolBar-cancel:active:not(:disabled) { + background-color: var(--cancel-button-active); + background-image: none; } -.dome-window-inactive .dome-xToolBar-control:disabled { - fill: #ccc ; - color: #ccc ; +/* Disabled */ + +.dome-xToolBar-control:disabled { + fill: var(--disabled-text) ; + color: var(--disabled-text) ; } /* Selected */ -.dome-window-active .dome-xToolBar-control.dome-selected { - fill: #fff; - color: #fff; +.dome-xToolBar-control.dome-selected { + fill: var(--info-text); + color: var(--info-text); border: 1px solid transparent ; - background-color: #7f7f7f ; + background-color: var(--selected-button-img) ; background-image: none ; } -.dome-window-active .dome-xToolBar-control.dome-selected:hover { - background-color: #888 ; +.dome-xToolBar-control.dome-selected:hover { + background-color: var(--selected-button-hover) ; background-image: none ; } -.dome-window-inactive .dome-xToolBar-control.dome-selected:not(:disabled) { - fill: #eee; - color: #eee; - background-color: #ccc ; -} - /* Selected & Disabled */ -.dome-window-active .dome-xToolBar-control.dome-selected:disabled { - fill: #ccc ; - color: #ccc ; - border: 1px solid #bbb ; - background-color: #ddd ; -} - -.dome-window-inactive .dome-xToolBar-control.dome-selected:disabled { - fill: #ccc ; - color: #ccc ; - border-color: #ddd ; - background-color: #ddd ; +.dome-xToolBar-control.dome-selected:disabled { + fill: var(--info-text) ; + color: var(--info-text) ; + border: 1px solid var(--border-discrete) ; } /* -------------------------------------------------------------------------- */ @@ -419,28 +393,17 @@ .dome-xToolBar-group .dome-xToolBar-control { margin: 0 ; -} - -.dome-xToolBar-group .dome-xToolBar-control:not(:first-child) { - border-left: 0 ; + border-radius: 0; } .dome-xToolBar-group > .dome-xToolBar-control:first-child { - border-top-right-radius: 0 ; - border-bottom-right-radius: 0 ; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; } .dome-xToolBar-group > .dome-xToolBar-control:last-child { - border-top-left-radius: 0 ; - border-bottom-left-radius: 0 ; -} - -.dome-xToolBar-group > .dome-xToolBar-control:first-child:last-child { - border-radius: 4px ; -} - -.dome-xToolBar-group > .dome-xToolBar-control:not(:first-child):not(:last-child) { - border-radius: 0 ; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; } /* -------------------------------------------------------------------------- */ @@ -449,21 +412,15 @@ .dome-xToolBar-control.dome-xToolBar-searchfield { background-image: none ; - background-color: #eee ; - margin-top: 1px ; - padding-top: 1px ; + background-color: var(--background-alterning-odd); padding-left: 20px ; border-radius: 12px ; - border-color: #aaa ; + border-color: var(--border); + color: var(--text); width: 32px ; transition: width 0.4s ease-in-out ; } -.dome-window-inactive .dome-xToolBar-control.dome-xToolBar-searchfield { - background-color: #f6f6f6 ; - border-color: #ddd ; -} - .dome-xToolBar-searchfield:focus, .dome-xToolBar-searchfield:hover, .dome-xToolBar-searchicon:hover + .dome-xToolBar-searchfield @@ -477,14 +434,10 @@ z-Index: +1; height: 0px; width: 0px; - top: 2px ; + top: -7px ; left: 12px ; } -.dome-window-inactive .dome-xToolBar-searchicon svg { - fill: #ccc ; -} - .dome-xToolBar-searchmenu { position: relative ; width: 162px ; @@ -494,11 +447,12 @@ left: -7px ; top: 4px ; padding: 0px ; - border: 1pt solid #aaa ; - background: #fff ; + border: 1pt solid var(--border) ; + background: var(--background-alterning-odd) ; } .dome-xToolBar-searchitem { + color: var(--text); display: block ; width: 100% ; margin-left: 0px ; @@ -510,11 +464,55 @@ .dome-xToolBar-searchitem:hover, .dome-xToolBar-searchindex { - background: lightblue !important; + background: var(--selected-element) !important; } .dome-xToolBar-searchitem:nth-child(even) { - background: #eef7f7; + background: var(--background-alterning-even); } /* -------------------------------------------------------------------------- */ + +.dome-xSwitch { + position: relative; + display: inline-block; + width: 34px; + height: 21px; +} + +.dome-xSwitch input { + visibility: hidden; +} + +.dome-xSwitch-slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--background-softer); + box-shadow: 0 0 1px var(--border); + -webkit-transition: .4s; + transition: .4s; + border-radius: 21px; +} + +.dome-xSwitch-slider:before { + position: absolute; + content: ""; + height: 15px; + width: 15px; + left: 3px; + bottom: 3px; + background-color: var(--text-discrete); + -webkit-transition: .4s; + transition: .4s; + border-radius: 50%; +} + +input:checked + .dome-xSwitch-slider:before { + -webkit-transform: translateX(13px); + -ms-transform: translateX(13px); + transform: translateX(13px); +} diff --git a/ivette/src/dome/renderer/frame/toolbars.tsx b/ivette/src/dome/renderer/frame/toolbars.tsx index ab7614418d2b6d9d9122808997ea92c6a81df34a..44f69e6012a22edccb1e207f77f3b0ebfd15977f 100644 --- a/ivette/src/dome/renderer/frame/toolbars.tsx +++ b/ivette/src/dome/renderer/frame/toolbars.tsx @@ -149,6 +149,39 @@ export function Button<A = undefined>(props: ButtonProps<A>) { ); } +export interface SwitchProps { + /** Switch tooltip. */ + title?: string; + /** Additional class. */ + className?: string; + /** Additional style. */ + style?: React.CSSProperties; + /** Defaults to `true`. */ + enabled?: boolean; + /** Defaults to `false`. */ + disabled?: boolean; + /** Switch position. Defaults to 'left'. */ + position?: 'left' | 'right'; + /** Click callback. */ + onChange?: (newPosition: 'left' | 'right') => void; +} + +/** Toolbar Left/Right Switch. */ +export function Switch(props: SwitchProps) { + const { position, onChange } = props; + const checked = position === 'right'; + const { title, className, style } = props; + const { enabled = true, disabled = false } = props; + const callback = onChange && (() => onChange(checked ? 'left' : 'right')); + if (disabled || !enabled) return null; + return ( + <label className={classes('dome-xSwitch', className)} style={style}> + <input type={'checkbox'} checked={checked} onChange={callback} /> + <span className={'dome-xSwitch-slider'} title={title} /> + </label > + ); +} + // -------------------------------------------------------------------------- // --- Selection Props // -------------------------------------------------------------------------- diff --git a/ivette/src/dome/renderer/layout/forms.tsx b/ivette/src/dome/renderer/layout/forms.tsx index 8ea1737f70d8282f2803b2a35b392ae5cf47f51e..135b474d5a9b13fa9e74c85d0b591a4568aafa39 100644 --- a/ivette/src/dome/renderer/layout/forms.tsx +++ b/ivette/src/dome/renderer/layout/forms.tsx @@ -1249,6 +1249,7 @@ export function SelectField(props: SelectFieldProps) { return ( <Field {...props} + offset={5} error={error} htmlFor={id} > @@ -1265,4 +1266,67 @@ export function SelectField(props: SelectFieldProps) { ); } +/** @category Form Fields */ +export interface MenuFieldOption<A> { + value: A; + label: string; +} + +/** @category Form Fields */ +export interface MenuFieldProps<A> extends FieldProps<A> { + /** Field label. */ + label: string; + /** Field tooltip text. */ + title?: string; + /** Field state. */ + state: FieldState<A>; + placeholder?: string; + defaultValue: A; + options: MenuFieldOption<A>[]; +} + +type ENTRY<A> = { option: JSX.Element, field: string, value: A }; + +/** + Creates a `<SelectField/>` form field with a predefine set + of (typed) options. + + @category Form Fields + */ +export function MenuField<A>(props: MenuFieldProps<A>): JSX.Element { + const entries: ENTRY<A>[] = React.useMemo(() => + props.options.map((e, k) => { + const field = `item#${k}`; + const option = <option value={field} key={field} label={e.label} />; + return { field, option, value: e.value }; + }), [props.options]); + const input = React.useCallback( + (v) => entries.find((e) => e.value === v)?.field + , [entries] + ); + const output = React.useCallback( + (f) => entries.find((e) => e.field === f)?.value ?? props.defaultValue + , [entries, props.defaultValue] + ); + const defaultField = React.useMemo( + () => input(props.defaultValue), + [input, props.defaultValue] + ); + const state = useFilter<A, string | undefined>( + props.state, + input, output, + defaultField, + ); + return ( + <SelectField + state={state} + label={props.label} + title={props.title} + placeholder={props.placeholder} > + {entries.map((e) => e.option)} + </SelectField> + ); +} + + // -------------------------------------------------------------------------- diff --git a/ivette/src/dome/renderer/layout/style.css b/ivette/src/dome/renderer/layout/style.css index cdfc2ac1e86d4863b281d1fbfc9f2b40ef9166e4..d33ea6685cbc285fc8094851ae76b174398efd34 100644 --- a/ivette/src/dome/renderer/layout/style.css +++ b/ivette/src/dome/renderer/layout/style.css @@ -185,16 +185,10 @@ .dome-xSplitter-vpos-R { position: absolute; height: 1px; width: 100% } .dome-xSplitter-vpos-B { position: absolute; bottom: 0px; width: 100% } -.dome-window-active .dome-xSplitter-hline, -.dome-window-active .dome-xSplitter-vline +.dome-xSplitter-hline, +.dome-xSplitter-vline { - background: #afafaf ; -} - -.dome-window-inactive .dome-xSplitter-hline, -.dome-window-inactive .dome-xSplitter-vline -{ - background: #d6d6d6 ; + background: var(--splitter) ; } /* -------------------------------------------------------------------------- */ @@ -223,7 +217,7 @@ z-index: 2 ; position: absolute ; border-radius: 12px ; - background: #64b4f0 ; + background: var(--grid-layout-target) ; opacity: 0.5 ; } @@ -231,7 +225,7 @@ z-index: -1 ; position: absolute ; overflow: hidden ; - background: lightblue ; + background: var(--grid-layout-holder) ; opacity: 0.5 ; } @@ -313,7 +307,7 @@ } .dome-xForm-section .dome-disabled { - color: #aaa ; + color: var(--background-disabled) ; } .dome-xForm-hsep @@ -327,7 +321,7 @@ { position: absolute ; top: 2px ; - fill: #888 ; + fill: var(--info-text-discrete) ; left: -14px ; } @@ -350,7 +344,7 @@ .dome-xForm-label.dome-disabled, .dome-xForm-field.dome-disabled { - color: #aaa ; + color: var(--background-disabled) ; opacity: 0.9 ; } @@ -363,7 +357,7 @@ { position: absolute ; top: -1px ; - fill: darkorange ; + fill: var(--error) ; margin-left: 6px ; } @@ -374,7 +368,7 @@ position: absolute ; top: 3px ; width: max-content ; - color: darkorange ; + color: var(--warning) ; margin-left: 4px ; font-size: smaller ; } @@ -401,7 +395,7 @@ .dome-xForm-textarea-field { - border-color: #d6d6d6 ; + border-color: var(--border-discrete) ; font-size: 10pt ; padding-top: 2px ; padding-bottom: 2px ; @@ -435,7 +429,7 @@ .dome-xForm-units { - color: #888 ; + color: var(--info-text-discrete) ; font-size: smaller ; margin-left: 4px ; } diff --git a/ivette/src/dome/renderer/light.css b/ivette/src/dome/renderer/light.css new file mode 100644 index 0000000000000000000000000000000000000000..e70918a90f1e1f35a7b2ac7be6a7cbce40a9e394 --- /dev/null +++ b/ivette/src/dome/renderer/light.css @@ -0,0 +1,102 @@ +@media (prefers-color-scheme: light) { + :root { + --text: #333333; + --text-discrete: #606060; + --disabled-text: #b0b0b0; + --info-text: #777; + --info-text-discrete: #888; + --text-highlighted: blue; + + --code-hover: lightgreen; + --code-select: #ffda95; + --code-select-hover: orange; + --dead-code: #bbb; + --non-terminating: #bbb; + --highlighted-marker: #ffff66; + --code-bullet: #ddd; + + --splitter: #aaa; + --border: #afafaf; + --border-discrete: #d0d0d0; + + --background: #e6e6e6; + --background-profound: #c0c0c0; + --background-profound-hover: #c6c6c6; + --background-disabled: #a0a0a0; + --background-disabled-hover: #a6a6a6; + --background-intense: #dcdcdc; + --background-softer: #f0f0f0; + --background-sidebar: #e3e8ec; + --background-button-hover: #c0c0c0; + --background-alterning-odd: #fdfdfd; + --background-alterning-even: #efefef; + --background-interaction: white; + --background-report: white; + + --selected-element: #8ce0fb; + --checked-element: #54abef; + + --lcd-button-color: #61611a; + --lcd-button-background: #b2b798; + + --default-button-color: #e8e8e8; + --default-led-img: radial-gradient(circle at center, #9fd2f9, #81c9ff); + --default-button-img: linear-gradient(to bottom, #e8e8e8 0%, #f1f1f1 100%); + --default-button-hover: #ffffff; + --default-button-active: #f9f9f9; + + --primary-button-img: linear-gradient(to bottom, #449bef 0%, #4990e2 100%); + --primary-button-hover: #00b6ff; + --primary-button-active: #ddd; + + --positive-button-color: #34ff52; + --positive-led-img: radial-gradient(circle at center, #50c140, #279c16); + --positive-button-img: linear-gradient(to bottom, #34ff52 0%, #48fd64 100%); + --positive-button-hover: #ffffff; + --positive-button-active: #ffffff; + + --negative-button-color: #ec453e; + --negative-led-img: radial-gradient(circle at center, #ff5b56, #f75a55c7); + --negative-button-img: linear-gradient(to bottom, #ec453e 0%, #ff4c47 100%); + --negative-button-hover: red; + --negative-button-active: #ffffff; + + --warning-button-color: #fece72; + --warning-led-img: radial-gradient(circle at center, #ffc749, #ecd44f); + --warning-button-img: linear-gradient(to bottom, #fece72 0%, #fcaa0e 100%); + --warning-button-hover: orange; + --warning-button-active: #ffffff; + + --cancel-button-img: #c0c0c0; + --cancel-button-hover: #c4c4c4; + --cancel-button-active: #bcbcbc; + + --selected-button-img: #afafaf; + --selected-button-hover: #a9a9a9; + + --activated-button-color: #449bef; + --activated-button-hover: #c54dae; + + --grid-layout-holder: lightblue; + --grid-layout-target: #64b4f0; + + --error: darkorange; + --warning: darkorange; + + --meter-optimum: linear-gradient(to bottom, #4a0 0%, #8f0 20%, #4a0 100%); + --meter-subopti: linear-gradient(to bottom, #aa0 0%, #ff0 20%, #aa0 100%); + --meter-evenless: linear-gradient(to bottom, #a40 0%, #f80 20%, #a40 100%); + + --eva-state-before: #95f5ff; + --eva-state-after: #fff819; + --eva-state-then: #c8e06e; + --eva-state-else: #ff834e; + --eva-probes-pinned: #cbe4cb; + --eva-probes-pinned-focused: #02da02; + --eva-probes-transient: #fff0d5; + --eva-probes-transient-focused: orange; + --eva-alarms-true: green; + --eva-alarms-false: #fb4e4a; + --eva-alarms-unknown: darkorange; + } +} diff --git a/ivette/src/dome/renderer/style.css b/ivette/src/dome/renderer/style.css index 258fc4e47c1825f058b4c28c876ba0265e93542f..6caf974d1c9b5d056c18fe00b509c172d120bc7a 100644 --- a/ivette/src/dome/renderer/style.css +++ b/ivette/src/dome/renderer/style.css @@ -3,28 +3,28 @@ /* -------------------------------------------------------------------------- */ * { - user-select: none; - box-sizing: border-box; - margin: 0 ; - padding: 0 ; - } + user-select: none; + box-sizing: border-box; + margin: 0 ; + padding: 0 ; +} body { - overflow: hidden ; - position: fixed ; - font-family: sans-serif ; - font-size: 13px ; - color: #333333 ; - background: #f0f0f0 ; - top: 0 ; - bottom: 0 ; - left: 0 ; - right: 0 ; + color: var(--text); + background: var(--background-softer); + overflow: hidden ; + position: fixed ; + font-family: sans-serif ; + font-size: 13px ; + top: 0 ; + bottom: 0 ; + left: 0 ; + right: 0 ; } #app { - width: 100% ; - height: 100% ; + width: 100% ; + height: 100% ; } /* -------------------------------------------------------------------------- */ @@ -39,29 +39,11 @@ body { visibility: hidden; } -.dome-window-active .dome-color-frame { - fill: #606060 ; - color: #606060 ; - border-color: #afafaf ; - background: #d8d8d8 ; -} - -.dome-window-inactive .dome-color-frame { - fill: #b0b0b0 ; - color: #b0b0b0 ; - border-color: #d6d6d6 ; - background: #f6f6f6 ; -} - -.dome-window-active .dome-color-selected { - border-color: #e8e8e8 ; - background: #ff9504 ; - color: #ffffff ; -} - -.dome-window-inactive .dome-color-selected { - border-color: #e8e8e8 ; - background: #d8d8d8 ; +.dome-color-frame { + fill: var(--text-discrete) ; + color: var(--text-discrete) ; + border-color: var(--border) ; + background: var(--background-intense) ; } .dome-color-dragzone { @@ -71,19 +53,19 @@ body { } .dome-color-dragzone:hover { - background: #808080 ; - opacity: 0.2 ; + background: var(--grid-layout-holder) ; + opacity: 0.4 ; transition: opacity .1s linear 0.1s , background .1s linear 0.1s ; } .dome-color-dragging { - background: #64b4f0 ; + background: var(--grid-layout-target) ; opacity: 0.5 ; transition: opacity .1s linear 0.1s , background .1s linear 0.1s ; } div.dome-dragged { - background: lightblue ; + background: var(--grid-layout-holder) ; border: none ; } @@ -146,3 +128,72 @@ div.dome-dragged { } /* -------------------------------------------------------------------------- */ +/* --- Theme-compliant Scrollbars --- */ +/* -------------------------------------------------------------------------- */ + +::-webkit-scrollbar { + width: 14px; + height: 14px; +} + +::-webkit-scrollbar-track { + background: var(--background-intense); +} + +::-webkit-scrollbar-thumb { + background-color: var(--info-text-discrete); + border-radius: 20px; + border: 3px solid var(--background-intense); +} + +::-webkit-scrollbar-corner { + background-color: var(--background-profound); + background: var(--background-profound); + color: var(--background-profound); +} + +/* -------------------------------------------------------------------------- */ +/* --- Theme-compliant Input Widgets --- */ +/* -------------------------------------------------------------------------- */ + +input[type="search"]::placeholder { + font-style: italic; + color: var(--text-discrete); +} + +input[type="text"]::placeholder { + font-style: italic; + color: var(--text-discrete); +} + +input[type="text"] { + vertical-align: middle; + margin: 2px 4px 2px 0px; + background-color: var(--background-interaction); + border: var(--border); + border-radius: 2px; +} + +input:focus-visible { + outline: none; + box-shadow: 0px 0px 1px 1px var(--border); +} + +input[type="checkbox"] { + appearance: none; + width: 13px; + height: 13px; + border: 1px solid var(--border); + border-radius: 2px; + content: ""; + font-size: 12px; + color: var(--text); + background-clip: content-box; + padding: 1px; +} + +input[type="checkbox"]:checked { + background-color: var(--checked-element); +} + +/* -------------------------------------------------------------------------- */ diff --git a/ivette/src/dome/renderer/table/style.css b/ivette/src/dome/renderer/table/style.css index aefbe542e53a14750f730b645303516c8a51ecb5..4b632757b3d7cfa863a3fccd15ea4704ec6c54ff 100644 --- a/ivette/src/dome/renderer/table/style.css +++ b/ivette/src/dome/renderer/table/style.css @@ -19,9 +19,9 @@ .dome-xTable .ReactVirtualized__Table__headerRow { display: flex ; align-items: stretch ; - background: #eee ; - color: #555 ; - fill: #777 ; + background: var(--background-intense) ; + color: var(--text-discrete) ; + fill: var(--info-text) ; } .dome-xTable .ReactVirtualized__Table__headerColumn { @@ -29,7 +29,7 @@ padding-left: 4px ; padding-right: 4px ; margin: 0px ; /* Necessary for column alignement */ - border-color: #cfcfcf ; + border-color: var(--border-discrete) ; border-bottom-width: 1px ; border-bottom-style: solid ; } @@ -100,20 +100,16 @@ vertical-align: baseline ; } -.dome-window-active .dome-xTable-selected { - background: #8ce0fb ; -} - -.dome-window-inactive .dome-xTable-selected { - background: #ccc ; +.dome-xTable-selected { + background: var(--selected-element) ; } .dome-xTable-odd { - background: #fdfdfd ; + background: var(--background-alterning-odd) ; } .dome-xTable-even { - background: #efefef ; + background: var(--background-alterning-even) ; } .ReactVirtualized__Table__row.dome-color-selected { diff --git a/ivette/src/dome/renderer/text/dark-code.css b/ivette/src/dome/renderer/text/dark-code.css new file mode 100644 index 0000000000000000000000000000000000000000..660ee1a0e5cd3f76a4b97be05c2400036ae7d15b --- /dev/null +++ b/ivette/src/dome/renderer/text/dark-code.css @@ -0,0 +1,57 @@ +.cm-s-dark-code.CodeMirror, .cm-s-dark-code .CodeMirror-gutters { + background-color: #1e2b3d; + color: var(--text-discrete); +} + +.cm-s-dark-code .CodeMirror-gutters { + background: #1e2b3d; + border-right: 0px; +} + +.cm-s-dark-code .CodeMirror-linenumber { + color: var(--disabled-text); +} + +.cm-s-dark-code .CodeMirror-cursor { + border-left: 1px solid var(--text); +} + +.cm-s-dark-code.cm-fat-cursor .CodeMirror-cursor { + background-color: #8e8d8875 !important; +} + +.cm-s-dark-code .cm-animate-fat-cursor { + background-color: #8e8d8875 !important; +} + + +.cm-s-dark-code div.CodeMirror-selected { + background: #3e4b5d; +} + +.cm-s-dark-code span.cm-meta { color: #83a598; } + +.cm-s-dark-code span.cm-comment { color: var(--info-text); } +.cm-s-dark-code span.cm-number, span.cm-atom { color: #d3869b; } +.cm-s-dark-code span.cm-keyword { color: #d84954; } + +.cm-s-dark-code span.cm-variable { color: var(--text); } +.cm-s-dark-code span.cm-variable-2 { color: var(--text); } +.cm-s-dark-code span.cm-variable-3, .cm-s-dark-code span.cm-type { color: #bacd5f; } +.cm-s-dark-code span.cm-operator { color: var(--text); } +.cm-s-dark-code span.cm-callee { color: var(--text); } +.cm-s-dark-code span.cm-def { color: var(--text); } +.cm-s-dark-code span.cm-property { color: var(--text); } +.cm-s-dark-code span.cm-string { color: #93669b; } +.cm-s-dark-code span.cm-string-2 { color: #8ec07c; } +.cm-s-dark-code span.cm-qualifier { color: #8ec07c; } +.cm-s-dark-code span.cm-attribute { color: #8ec07c; } + +.cm-s-dark-code .CodeMirror-activeline-background { background: var(--background); } +.cm-s-dark-code .CodeMirror-matchingbracket { background: #928374; color:#282828 !important; } +.cm-s-dark-code .CodeMirror-scrollbar-filler { + background-color: var(--background-profound); +} + +.cm-s-dark-code span.cm-builtin { color: #fe8039; } +.cm-s-dark-code span.cm-tag { color: #fe8039; } diff --git a/ivette/src/dome/renderer/text/editors.tsx b/ivette/src/dome/renderer/text/editors.tsx index b3cf82b06d31f0111a5ef01cf098f0c772e1adf2..43df5396e92fe069852ce49cdfa8aab940e908ef 100644 --- a/ivette/src/dome/renderer/text/editors.tsx +++ b/ivette/src/dome/renderer/text/editors.tsx @@ -34,11 +34,13 @@ import _ from 'lodash'; import React from 'react'; import * as Dome from 'dome'; +import * as Themes from 'dome/themes'; import { Vfill } from 'dome/layout/boxes'; import CodeMirror, { EditorConfiguration } from 'codemirror/lib/codemirror'; import { RichTextBuffer, CSSMarker, Decorator } from './buffers'; import './style.css'; +import './dark-code.css'; import 'codemirror/lib/codemirror.css'; const CSS_HOVERED = 'dome-xText-hover'; @@ -487,8 +489,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { // --- Text View // -------------------------------------------------------------------------- -/** - #### Text Editor. +/** #### Text Editor. A component rendering the content of a text buffer, that shall be instances of the `Buffer` base class. @@ -506,11 +507,17 @@ class CodeMirrorWrapper extends React.Component<TextProps> { #### Themes - The CodeMirror `theme` option allow you to style your document, - especially when using modes. - Themes are only accessible if you load the associated CSS style sheet. - For instance, to use the `'ambiance'` theme provided with CodeMirror, you - shall import `'codemirror/theme/ambiance.css'` somewhere in your application. + The CodeMirror `theme` option allow you to style your document, especially + when using modes. + + By default, CodeMirror uses the `'default'` theme in _light_ theme and the + `'dark-code'` theme in _dark_ theme. The `'dark-code'` is provided by Dome, + Cf. `./dark-mode.css` in the source distribution. + + To use other custom themes, you shall load the associated CSS style + sheet. For instance, to use the `'ambiance'` theme provided with CodeMirror, + you shall import `'codemirror/theme/ambiance.css'` somewhere in your + application. #### Modes & Adds-On @@ -524,16 +531,17 @@ class CodeMirrorWrapper extends React.Component<TextProps> { You can register your own extensions directly into the global `CodeMirror` class instance. However, the correct instance must be retrieved by using `import CodeMirror from 'codemirror/lib/codemirror.js'` ; using `from - 'codemirror'` returns a different instance of `CodeMirror` class and will - not work. - */ + 'codemirror'` returns a different instance of `CodeMirror` class and will not + work. */ export function Text(props: TextProps) { - let { className, style, fontSize, ...cmprops } = props; + const [appTheme] = Themes.useColorTheme(); + let { className, style, fontSize, theme: usrTheme, ...cmprops } = props; if (fontSize !== undefined && fontSize < 4) fontSize = 4; if (fontSize !== undefined && fontSize > 48) fontSize = 48; + const theme = usrTheme ?? (appTheme === 'dark' ? 'dark-code' : 'default'); return ( <Vfill className={className} style={{ ...style, fontSize }}> - <CodeMirrorWrapper fontSize={fontSize} {...cmprops} /> + <CodeMirrorWrapper fontSize={fontSize} theme={theme} {...cmprops} /> </Vfill> ); } diff --git a/ivette/src/dome/renderer/text/style.css b/ivette/src/dome/renderer/text/style.css index 425a00b769c4956576bc4f332ba3eff52106bafb..3dd0c7e07f48e46a4a51589d0fbbfa012e3617ad 100644 --- a/ivette/src/dome/renderer/text/style.css +++ b/ivette/src/dome/renderer/text/style.css @@ -17,15 +17,15 @@ } .dome-xText-hover { - background: lightgreen ; + background: var(--code-hover) ; } .dome-xText-select { - background: #ffda95 !important ; + background: var(--code-select) !important ; } .dome-xText-select.dome-xText-hover { - background: orange !important ; + background: var(--code-select-hover) !important ; } /* -------------------------------------------------------------------------- */ @@ -33,7 +33,6 @@ /* -------------------------------------------------------------------------- */ .dome-xPages-note { - background: white ; padding: 2px ; } @@ -48,9 +47,7 @@ height: calc(100% - 16px) ; margin: 8px ; padding: 8px ; - background: white ; border-radius: 2px ; - box-shadow: 2px 2px 6px 3px lightgray ; overflow: auto ; } diff --git a/ivette/src/dome/renderer/themes.tsx b/ivette/src/dome/renderer/themes.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fc24cb2bd4ec9f1fea98e3c17a7bde85eb131c3d --- /dev/null +++ b/ivette/src/dome/renderer/themes.tsx @@ -0,0 +1,84 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2021 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +// -------------------------------------------------------------------------- +// --- Global Color Theme Management +// -------------------------------------------------------------------------- + +/** + @packageDocumentation + @module dome/themes + */ + +//import React from 'react'; +import * as Dome from 'dome'; +import * as Settings from 'dome/data/settings'; +import { State } from 'dome/data/states'; +import { ipcRenderer } from 'electron'; + +/* -------------------------------------------------------------------------- */ +/* --- Global Settings --- */ +/* -------------------------------------------------------------------------- */ + +export type ColorTheme = 'dark' | 'light'; +export type ColorSettings = 'dark' | 'light' | 'system'; + +export const jColorTheme = + (th: string | undefined): ColorTheme => (th === 'dark' ? 'dark' : 'light'); +export const jColorSettings = + (th: string | undefined): ColorSettings => { + switch (th) { + case 'light': + case 'dark': + case 'system': + return th; + default: + return 'system'; + } + }; + +const ColorThemeSettings = new Settings.GString('dome-color-theme', 'system'); +const NativeThemeUpdated = new Dome.Event('dome.theme.updated'); +ipcRenderer.on('dome.theme.updated', () => NativeThemeUpdated.emit()); + +async function getNativeTheme(): Promise<ColorTheme> { + const th = await ipcRenderer.invoke('dome.ipc.theme'); + return jColorTheme(th); +} + +/* -------------------------------------------------------------------------- */ +/* --- Color Theme Hooks --- */ +/* -------------------------------------------------------------------------- */ + +export function useColorTheme(): [ColorTheme, (upd: ColorSettings) => void] { + Dome.useUpdate(NativeThemeUpdated); + const { result: current } = Dome.usePromise(getNativeTheme()); + const [pref, setTheme] = Settings.useGlobalSettings(ColorThemeSettings); + return [current ?? jColorTheme(pref), setTheme]; +} + +export function useColorThemeSettings(): State<ColorSettings> { + const [pref, setTheme] = Settings.useGlobalSettings(ColorThemeSettings); + return [jColorSettings(pref), setTheme]; +} + +/* -------------------------------------------------------------------------- */ diff --git a/ivette/src/frama-c/kernel/ASTinfo.tsx b/ivette/src/frama-c/kernel/ASTinfo.tsx index a925bbaf18b9ac5a9475220692abb922147d31e3..928411368bdae0c159d30feae7ece4330b2bd0c0 100644 --- a/ivette/src/frama-c/kernel/ASTinfo.tsx +++ b/ivette/src/frama-c/kernel/ASTinfo.tsx @@ -46,9 +46,8 @@ export default function ASTinfo(): JSX.Element { React.useEffect(() => { buffer.clear(); - if (data) { - Utils.printTextWithTags(buffer, data, { css: 'color: blue' }); - } + const style = { css: 'color: var(--text-highlighted)' }; + if (data) Utils.printTextWithTags(buffer, data, style); }, [buffer, data]); // Callbacks @@ -65,7 +64,6 @@ export default function ASTinfo(): JSX.Element { <Text buffer={buffer} mode="text" - theme="default" onSelection={onTextSelection} readOnly /> diff --git a/ivette/src/frama-c/kernel/ASTview.tsx b/ivette/src/frama-c/kernel/ASTview.tsx index d0a75469a82c7f403d2f2cf908a5831f03beb9be..31dc6fd65b1ee9093d73ea72d0444595374661c6 100644 --- a/ivette/src/frama-c/kernel/ASTview.tsx +++ b/ivette/src/frama-c/kernel/ASTview.tsx @@ -164,10 +164,8 @@ export default function ASTview() { const multipleSelections = selection?.multiple.allSelections; const theFunction = selection?.current?.fct; const theMarker = selection?.current?.marker; - const { buttons: themeButtons, theme, fontSize, wrapText } = - Preferences.useThemeButtons({ - target: 'Internal AST', - theme: Preferences.AstTheme, + const { buttons: editorButtons, fontSize, wrapText } = + Preferences.useEditorButtons({ fontSize: Preferences.AstFontSize, wrapText: Preferences.AstWrapText, disabled: !theFunction, @@ -306,12 +304,11 @@ export default function ASTview() { return ( <> <TitleBar> - {themeButtons} + {editorButtons} </TitleBar> <Text buffer={buffer} mode="text/x-csrc" - theme={theme} fontSize={fontSize} lineWrapping={wrapText} selection={theMarker} diff --git a/ivette/src/frama-c/kernel/PivotTable-style.css b/ivette/src/frama-c/kernel/PivotTable-style.css new file mode 100644 index 0000000000000000000000000000000000000000..afb410b1882c753060084b0ed8a2aa35bd982801 --- /dev/null +++ b/ivette/src/frama-c/kernel/PivotTable-style.css @@ -0,0 +1,324 @@ +.pvtUi { + color: var(--text); + border-collapse: collapse; +} +.pvtUi select { + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -khtml-user-select: none; + -ms-user-select: none; +} + +.pvtUi td.pvtOutput { + vertical-align: top; +} + +table.pvtTable { + font-size: 8pt; + text-align: left; + border-collapse: collapse; + margin-top: 3px; + margin-left: 3px; +} +table.pvtTable thead tr th, +table.pvtTable tbody tr th { + background-color: var(--background-intense); + border: 1px solid var(--border); + font-size: 8pt; + padding: 5px; +} + +table.pvtTable .pvtColLabel { + text-align: center; +} +table.pvtTable .pvtTotalLabel { + text-align: right; +} + +table.pvtTable tbody tr td { + color: var(--text); + padding: 5px; + background-color: var(--background-alterning-odd); + border: 1px solid var(--border); + vertical-align: top; + text-align: right; +} + +.pvtTotal, +.pvtGrandTotal { + font-weight: bold; +} + +.pvtRowOrder, +.pvtColOrder { + cursor: pointer; + width: 15px; + margin-left: 5px; + display: inline-block; + user-select: none; + text-decoration: none !important; + -webkit-user-select: none; + -moz-user-select: none; + -khtml-user-select: none; + -ms-user-select: none; +} + +.pvtAxisContainer, +.pvtVals { + border: 1px solid var(--border); + background: var(--background-softer); + padding: 5px; + min-width: 20px; + min-height: 20px; +} + +.pvtRenderers { + padding-left: 5px; + user-select: none; +} + +.pvtDropdown { + display: inline-block; + position: relative; + -webkit-user-select: none; + -moz-user-select: none; + -khtml-user-select: none; + -ms-user-select: none; + margin: 3px; +} + +.pvtDropdownIcon { + float: right; + color: var(--border-discrete); +} +.pvtDropdownCurrent { + text-align: left; + border: 1px solid var(--border-discrete); + border-radius: 4px; + display: inline-block; + position: relative; + width: 210px; + box-sizing: border-box; + background: var(--background-interaction); +} + +.pvtDropdownCurrentOpen { + border-radius: 4px 4px 0 0; +} + +.pvtDropdownMenu { + background: var(--background-alterning-odd); + position: absolute; + width: 100%; + margin-top: -1px; + border-radius: 0 0 4px 4px; + border: 1px solid var(--border-discrete); + border-top: 1px solid var(--border-discrete); + box-sizing: border-box; +} + +.pvtDropdownValue { + padding: 2px 5px; + font-size: 12px; + text-align: left; +} +.pvtDropdownActiveValue { + background: var(--selected-element); +} + +.pvtVals { + text-align: center; + white-space: nowrap; + vertical-align: top; + padding-bottom: 12px; +} + +.pvtRows { + height: 35px; +} + +.pvtAxisContainer li { + margin: 8px 6px; + list-style-type: none; + cursor: move; +} +.pvtAxisContainer li.pvtPlaceholder { + -webkit-border-radius: 5px; + padding: 3px 15px; + -moz-border-radius: 5px; + border-radius: 5px; + border: 1px dashed var(--border-discrete); +} +.pvtAxisContainer li.pvtPlaceholder span.pvtAttr { + display: none; +} + +.pvtAxisContainer li span.pvtAttr { + -webkit-text-size-adjust: 100%; + background: var(--background-interaction); + border: 1px solid var(--border); + padding: 2px 5px; + white-space: nowrap; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -khtml-user-select: none; + -ms-user-select: none; +} + +.pvtTriangle { + cursor: pointer; + color: var(--info-text-discrete); +} + +.pvtHorizList li { + display: inline-block; +} +.pvtVertList { + vertical-align: top; +} + +.pvtFilteredAttribute { + font-style: italic; +} + +.sortable-chosen .pvtFilterBox { + display: none !important; +} + +.pvtCloseX { + position: absolute; + right: 5px; + top: 5px; + font-size: 18px; + cursor: pointer; + text-decoration: none !important; +} + +.pvtDragHandle { + position: absolute; + left: 5px; + top: 5px; + font-size: 18px; + cursor: move; + color: var(--border-discrete); +} + +.pvtButton { + color: var(--text); + border-radius: 5px; + padding: 3px 6px; + background: var(--default-button-color); + border: 1px solid; + border-color: var(--border); + font-size: 14px; + margin: 3px; + transition: 0.34s all cubic-bezier(0.19, 1, 0.22, 1); + text-decoration: none !important; +} + +.pvtButton:hover { + background: var(--default-button-hover); + background-image: none; + border-color: var(--border-discrete); +} + +.pvtButton:active { + background: var(--default-button-active); + background-image: none; +} + +.pvtFilterBox input { + border: 1px solid var(--border-discrete); + border-radius: 5px; + color: var(--info-text-discrete); + padding: 0 3px; + font-size: 14px; +} + +.pvtFilterBox input:focus { + border-color: var(--border); + outline: none; +} + +.pvtFilterBox { + z-index: 100; + width: 300px; + border: 1px solid var(--border); + background-color: var(--background-report); + position: absolute; + text-align: center; + user-select: none; + min-height: 100px; + -webkit-user-select: none; + -moz-user-select: none; + -khtml-user-select: none; + -ms-user-select: none; +} + +.pvtFilterBox h4 { + margin: 15px; +} +.pvtFilterBox p { + margin: 10px auto; +} +.pvtFilterBox button { + color: var(--text); +} +.pvtFilterBox input[type='text'] { + width: 230px; + color: var(--text); + margin-bottom: 5px; +} + +.pvtCheckContainer { + text-align: left; + font-size: 14px; + white-space: nowrap; + overflow-y: scroll; + width: 100%; + max-height: 30vh; + border-top: 1px solid var(--border); +} + +.pvtCheckContainer p { + margin: 0; + margin-bottom: 1px; + padding: 3px; + cursor: default; +} + +.pvtCheckContainer p.selected { + background: var(--selected-element); +} + +.pvtOnly { + display: none; + width: 35px; + float: left; + font-size: 12px; + padding-left: 5px; + cursor: pointer; +} + +.pvtOnlySpacer { + display: block; + width: 35px; + float: left; +} + +.pvtCheckContainer p:hover .pvtOnly { + display: block; +} +.pvtCheckContainer p:hover .pvtOnlySpacer { + display: none; +} + +.pvtRendererArea { + padding: 5px; +} + diff --git a/ivette/src/frama-c/kernel/PivotTable.tsx b/ivette/src/frama-c/kernel/PivotTable.tsx index 1d1684ad1419de246d615abd0749e0d153aa6d41..f75b205c75eac827eb7daaf6e78f86e9bd16d07d 100644 --- a/ivette/src/frama-c/kernel/PivotTable.tsx +++ b/ivette/src/frama-c/kernel/PivotTable.tsx @@ -34,7 +34,7 @@ import * as Status from 'frama-c/kernel/Status'; import * as States from 'frama-c/states'; import * as PivotState from 'frama-c/plugins/pivot/api/general'; import PivotTableUI from 'react-pivottable/PivotTableUI'; -import 'react-pivottable/pivottable.css'; +import 'frama-c/kernel/PivotTable-style.css'; // -------------------------------------------------------------------------- // --- Pivot Table for Properties diff --git a/ivette/src/frama-c/kernel/SourceCode.tsx b/ivette/src/frama-c/kernel/SourceCode.tsx index 89a2dfcddcf94175bf3ee1628d60635ab9c64a14..a178a293277744671eec245f2ee2d0805ceceaa7 100644 --- a/ivette/src/frama-c/kernel/SourceCode.tsx +++ b/ivette/src/frama-c/kernel/SourceCode.tsx @@ -80,10 +80,8 @@ export default function SourceCode(): JSX.Element { const filename = Path.parse(file).base; // Title bar buttons, along with the parameters for our text. - const { buttons: themeButtons, theme, fontSize, wrapText } = - Preferences.useThemeButtons({ - target: 'Source Code', - theme: Preferences.SourceTheme, + const { buttons: editorButtons, fontSize, wrapText } = + Preferences.useEditorButtons({ fontSize: Preferences.SourceFontSize, wrapText: Preferences.AstWrapText, disabled: !theFunction, @@ -198,12 +196,11 @@ export default function SourceCode(): JSX.Element { /> <Code title={file} style={{ padding: '5px' }}>{filename}</Code> <Hfill /> - {themeButtons} + {editorButtons} </TitleBar> <Text buffer={buffer} mode="text/x-csrc" - theme={theme} fontSize={fontSize} lineWrapping={wrapText} selection={theMarker} diff --git a/ivette/src/frama-c/kernel/style.css b/ivette/src/frama-c/kernel/style.css index 295008738903d77b302382ab92371a4d20bf5ecb..1a6ef94f45373e357608c88fe24c0c57637b441c 100644 --- a/ivette/src/frama-c/kernel/style.css +++ b/ivette/src/frama-c/kernel/style.css @@ -14,21 +14,21 @@ /* -------------------------------------------------------------------------- */ .highlighted-marker { - background-color: #FFFF66; + background-color: var(--highlighted-marker); } .dead-code { - background-color: #BBB; - border-bottom: solid 0.1em #BBB; + background-color: var(--dead-code); + border-bottom: solid 0.1em var(--dead-code); } .non-terminating { - border-bottom: solid 0.2em #BBB; + border-bottom: solid 0.2em var(--non-terminating); } .bullet { width: 1.5em; - background: #DDD; + background: var(--code-bullet); } /* -------------------------------------------------------------------------- */ @@ -66,15 +66,6 @@ display: none; /* Hide label, only use placeholder */ } -.message-search input::placeholder { - font-style: italic; -} - -.message-search input { - vertical-align: middle; - margin: 2px 4px 2px 0px; -} - .message-search .dome-xForm-field { margin: 0px 4px; display: inline-block; @@ -101,6 +92,7 @@ .message-page { user-select: text; white-space: pre-line; + background-color: var(--background-report); } /* -------------------------------------------------------------------------- */ diff --git a/ivette/src/frama-c/plugins/eva/style.css b/ivette/src/frama-c/plugins/eva/style.css index ff501a3802314aa94355c99da2995d3ccfa77a47..3dd5be0b82191f7e234969da8d661bffaa387a1b 100644 --- a/ivette/src/frama-c/plugins/eva/style.css +++ b/ivette/src/frama-c/plugins/eva/style.css @@ -8,7 +8,7 @@ padding-top: 2px; padding-bottom: 4px; width: 100%; - background: #ccc; + background: var(--background-profound); display: flex; } @@ -21,10 +21,10 @@ .eva-probeinfo-code { flex: 0 1 auto; height: fit-content; - background: lightgrey; + background: var(--background-intense); min-width: 120px; width: auto; - border: thin solid black; + border: thin solid var(--border); padding-left: 6px; padding-right: 6px; border-radius: 4px; @@ -56,7 +56,7 @@ .eva-info { width: 100%; flex-wrap: wrap; - background: #ccc; + background: var(--background-profound); padding-top: 3px; padding-left: 12px; padding-bottom: 2px; @@ -64,19 +64,19 @@ .eva-callsite { flex: 0 0 auto; - fill: #7cacbb; - background: #eee; + fill: var(--selected-element); + background: var(--background-softer); border-radius: 4px; - border: thin solid black; + border: thin solid var(--border); padding-right: 7px; } .eva-callsite.eva-focused { - background: #cbe4cb; + background: var(--warning-button-color); } .eva-callsite.eva-focused.eva-transient { - background: #f9dca6; + background: var(--code-select); } /* -------------------------------------------------------------------------- */ @@ -98,16 +98,16 @@ } .eva-alarm-none { display: none; } -.eva-alarm-True { fill: green; } -.eva-alarm-False { fill: #fb4e4a; } -.eva-alarm-Unknown { fill: darkorange; } +.eva-alarm-True { fill: var(--eva-alarms-true); } +.eva-alarm-False { fill: var(--eva-alarms-false); } +.eva-alarm-Unknown { fill: var(--eva-alarms-unknown); } /* -------------------------------------------------------------------------- */ /* --- Styling Values --- */ /* -------------------------------------------------------------------------- */ .eva-stmt { - color: grey; + color: var(--info-text); text-select: none; } @@ -135,19 +135,19 @@ .eva-function { padding-top: 0px; - background: #ccc; + background: var(--background-profound); } .eva-head { padding-top: 2px; - color: #777; + color: var(--info-text); text-align: center; - border-left: thin solid black; - border-bottom: thin solid black; + border-left: thin solid var(--border); + border-bottom: thin solid var(--border); } .eva-probes .eva-head { - border-top: thin solid black; + border-top: thin solid var(--border); } .eva-phantom { @@ -172,8 +172,8 @@ .eva-cell { flex: 1 1 auto; - border-right: thin solid black; - border-bottom: thin solid black; + border-right: thin solid var(--border); + border-bottom: thin solid var(--border); overflow: hidden; display: flex; justify-content: center; @@ -194,13 +194,13 @@ } .eva-cell:nth-child(2) { - border-left: thin solid black; + border-left: thin solid var(--border); } .eva-probes .eva-cell { padding: 2px 4px; text-align: center; - border-top: thin solid black; + border-top: thin solid var(--border); } .eva-cell * { @@ -220,11 +220,11 @@ .eva-diff-added { } .eva-diff-removed { text-decoration: strike } -.eva-state-Before .eva-diff { background: #95f5ff; } -.eva-state-After .eva-diff { background: #fff819; } -.eva-state-Cond .eva-diff { background: #95f5ff; } -.eva-state-Then .eva-diff { background: #c8e06e; } -.eva-state-Else .eva-diff { background: #ff834e; } +.eva-state-Before .eva-diff { background: var(--eva-state-before); } +.eva-state-After .eva-diff { background: var(--eva-state-after); } +.eva-state-Cond .eva-diff { background: var(--eva-state-before); } +.eva-state-Then .eva-diff { background: var(--eva-state-then); } +.eva-state-Else .eva-diff { background: var(--eva-state-else); } /* -------------------------------------------------------------------------- */ /* --- Table Rows Background --- */ @@ -233,50 +233,43 @@ /* --- Probes --- */ .eva-probes .eva-cell { - background: #cbe4cb; + background: var(--eva-probes-pinned); } .eva-probes .eva-focused { - background: #02da02; + background: var(--eva-probes-pinned-focused); } .eva-probes .eva-transient { - background: #fff0d5; + background: var(--eva-probes-transient); + align-items: center; } .eva-probes .eva-transient.eva-focused { - background: orange; + background: var(--eva-probes-transient-focused); } /* --- Values / Callstacks --- */ .eva-values .eva-cell { - background: #eee; -} - -.eva-values .eva-focused { - background: #e1e8f7; -} - -.eva-values .eva-transient.eva-focused { - background: #fff0d5; + background: var(--background-alterning-odd); } .eva-callstack.eva-row-odd .eva-cell { - background: #eee; + background: var(--background-alterning-odd); } .eva-callstack.eva-row-even .eva-cell { - background: #e2e8e8; + background: var(--background-alterning-even); } .eva-callstack.eva-row-aligned { - background: lightblue; + background: var(--grid-layout-holder); } .eva-callstack.eva-row-selected { - background: #f9dca6; + background: var(--code-select); } /* -------------------------------------------------------------------------- */ diff --git a/ivette/src/frama-c/plugins/eva/summary.css b/ivette/src/frama-c/plugins/eva/summary.css index 41de1d074404dafbeb6a92b09769ffeed566c2dc..97233d3b4126459b0098db860ccbd45c6412f756 100644 --- a/ivette/src/frama-c/plugins/eva/summary.css +++ b/ivette/src/frama-c/plugins/eva/summary.css @@ -8,7 +8,6 @@ } .eva-summary-box { - background-color: white; height: 100%; width: 100%; display: flex; @@ -17,6 +16,7 @@ align-items: center; justify-content: center; overflow: auto; + background-color: var(--background-report); } .eva-summary { @@ -74,7 +74,7 @@ } .eva-summary .alarms-table tr:nth-child(2n) { - background-color: #eee; + background-color: var(--background-alterning-even); } .eva-summary .alarms-table td:nth-child(1) { @@ -86,7 +86,7 @@ } .eva-summary .statuses-table tr :nth-child(5) { - background-color: #ddd; + background-color: var(--background-intense); } .eva-summary td { diff --git a/ivette/src/ivette/prefs.tsx b/ivette/src/ivette/prefs.tsx index 3e887a0783447ce0f4f770877293cfeca2864c63..4ae1a9b2c009410f81c2af6449fe57eedcee3277 100644 --- a/ivette/src/ivette/prefs.tsx +++ b/ivette/src/ivette/prefs.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- Main React Component rendered by './index.js' // -------------------------------------------------------------------------- @@ -32,68 +30,66 @@ */ import React from 'react'; - -import { popupMenu } from 'dome'; +import * as Dome from 'dome'; +import * as Themes from 'dome/themes'; +import * as Toolbar from 'dome/frame/toolbars'; import * as Settings from 'dome/data/settings'; import { IconButton } from 'dome/controls/buttons'; - import 'codemirror/mode/clike/clike'; -import 'codemirror/theme/ambiance.css'; -import 'codemirror/theme/solarized.css'; - -export const THEMES = [ - { id: 'default', label: 'Default' }, - { id: 'ambiance', label: 'Ambiance' }, - { id: 'solarized light', label: 'Solarized Light' }, - { id: 'solarized dark', label: 'Solarized Dark' }, -]; // -------------------------------------------------------------------------- // --- AST View Preferences // -------------------------------------------------------------------------- -export const AstTheme = new Settings.GString('ASTview.theme', 'default'); export const AstFontSize = new Settings.GNumber('ASTview.fontSize', 12); export const AstWrapText = new Settings.GFalse('ASTview.wrapText'); - -export const SourceTheme = new Settings.GString('SourceCode.theme', 'default'); export const SourceFontSize = new Settings.GNumber('SourceCode.fontSize', 12); export const SourceWrapText = new Settings.GFalse('SourceCode.wrapText'); -export interface ThemeProps { - target: string; - theme: Settings.GlobalSettings<string>; - fontSize: Settings.GlobalSettings<number>; - wrapText: Settings.GlobalSettings<boolean>; - disabled?: boolean; +/* -------------------------------------------------------------------------- */ +/* --- Theme Switcher Button --- */ +/* -------------------------------------------------------------------------- */ + +export function ThemeSwitch(): JSX.Element { + const [theme, setTheme] = Themes.useColorTheme(); + const other = theme === 'dark' ? 'light' : 'dark'; + const position = theme === 'dark' ? 'left' : 'right'; + const title = `Switch to ${other} theme`; + const onChange = (): void => setTheme(other); + return ( + <Toolbar.Switch + disabled={!Dome.DEVEL} + title={title} + position={position} + onChange={onChange} + /> + ); } // -------------------------------------------------------------------------- -// --- Icon Buttons +// --- Editor Icon Buttons // -------------------------------------------------------------------------- -export interface ThemeControls { +export interface EditorProps { + fontSize: Settings.GlobalSettings<number>; + wrapText: Settings.GlobalSettings<boolean>; + disabled?: boolean; +} + +export interface EditorControls { buttons: React.ReactNode; - theme: string; fontSize: number; wrapText: boolean; } -export function useThemeButtons(props: ThemeProps): ThemeControls { - const [theme, setTheme] = Settings.useGlobalSettings(props.theme); +export function useEditorButtons(props: EditorProps): EditorControls { + const { disabled = false } = props; const [fontSize, setFontSize] = Settings.useGlobalSettings(props.fontSize); const [wrapText, setWrapText] = Settings.useGlobalSettings(props.wrapText); - const zoomIn = () => fontSize < 48 && setFontSize(fontSize + 2); - const zoomOut = () => fontSize > 4 && setFontSize(fontSize - 2); - const flipWrapText = () => setWrapText(!wrapText); - const selectTheme = (id?: string) => id && setTheme(id); - const themeItem = (th: { id: string; label: string }) => ( - { checked: th.id === theme, ...th } - ); - const themePopup = () => popupMenu(THEMES.map(themeItem), selectTheme); - const { disabled = false } = props; + const zoomIn = (): void => setFontSize(fontSize + 2); + const zoomOut = (): void => setFontSize(fontSize - 2); + const flipWrapText = (): void => setWrapText(!wrapText); return { - theme, fontSize, wrapText, buttons: [ @@ -101,6 +97,7 @@ export function useThemeButtons(props: ThemeProps): ThemeControls { key="zoom.out" icon="ZOOM.OUT" onClick={zoomOut} + enabled={fontSize > 4} disabled={disabled} title="Decrease font size" />, @@ -108,19 +105,15 @@ export function useThemeButtons(props: ThemeProps): ThemeControls { key="zoom.in" icon="ZOOM.IN" onClick={zoomIn} + enabled={fontSize < 48} disabled={disabled} title="Increase font size" />, - <IconButton - key="theme" - icon="PAINTBRUSH" - onClick={themePopup} - title="Choose theme" - />, <IconButton key="wrap" icon="WRAPTEXT" selected={wrapText} + disabled={disabled} onClick={flipWrapText} title="Wrap text" />, diff --git a/ivette/src/renderer/Application.tsx b/ivette/src/renderer/Application.tsx index 0717096732cd47e494951d5a81d5eb9cc600242a..ea4a07a1a0b540a7c66cc3708816d31926a9c692 100644 --- a/ivette/src/renderer/Application.tsx +++ b/ivette/src/renderer/Application.tsx @@ -35,6 +35,7 @@ import * as Sidebar from 'dome/frame/sidebars'; import * as Controller from './Controller'; import * as Extensions from './Extensions'; import * as Laboratory from './Laboratory'; +import * as IvettePrefs from 'ivette/prefs'; import './loader'; // -------------------------------------------------------------------------- @@ -50,7 +51,6 @@ export default function Application(): JSX.Element { const onSelectedHints = (): void => { if (hints.length === 1) Extensions.onSearchHint(hints[0]); }; - return ( <Vfill> <Toolbar.ToolBar> @@ -70,6 +70,7 @@ export default function Application(): JSX.Element { onHint={Extensions.onSearchHint} onSelect={onSelectedHints} /> + <IvettePrefs.ThemeSwitch /> <Toolbar.Button icon="ITEMS.GRID" title="Customize Main View" diff --git a/ivette/src/renderer/Controller.tsx b/ivette/src/renderer/Controller.tsx index 72b3d00388d46f0be483097c07fe3b3626ba3c52..5dde04e965faa98d5ec971575096eb8c58603725 100644 --- a/ivette/src/renderer/Controller.tsx +++ b/ivette/src/renderer/Controller.tsx @@ -37,12 +37,9 @@ import { LED, LEDstatus } from 'dome/controls/displays'; import { Label, Code } from 'dome/controls/labels'; import { RichTextBuffer } from 'dome/text/buffers'; import { Text } from 'dome/text/editors'; - import * as Ivette from 'ivette'; import * as Server from 'frama-c/server'; -import 'codemirror/theme/ambiance.css'; - // -------------------------------------------------------------------------- // --- Configure Server // -------------------------------------------------------------------------- @@ -76,7 +73,7 @@ function buildServerConfig(argv: string[], cwd?: string) { let command; let sockaddr; let cwdir = cwd; - for (let k = 0; k < argv.length; k++) { + for (let k = 0; k < (argv ? argv.length : 0); k++) { const v = argv[k]; switch (v) { case '--cwd': @@ -330,7 +327,6 @@ const RenderConsole = () => { buffer={edited ? editor : Server.buffer} mode="text" readOnly={!edited} - theme="ambiance" /> </> ); diff --git a/ivette/src/renderer/Preferences.tsx b/ivette/src/renderer/Preferences.tsx index 21f6a3b60b1f963a3498d21e53594651bfec3847..8056ec84f5ffae3c5f2b6f2d03afc5e77a00185a 100644 --- a/ivette/src/renderer/Preferences.tsx +++ b/ivette/src/renderer/Preferences.tsx @@ -39,35 +39,50 @@ import React from 'react'; import * as Settings from 'dome/data/settings'; import * as Forms from 'dome/layout/forms'; -import * as P from 'ivette/prefs'; +import * as Themes from 'dome/themes'; +import * as IvettePrefs from 'ivette/prefs'; // -------------------------------------------------------------------------- -// --- Font Forms +// --- Theme Fields // -------------------------------------------------------------------------- -function ThemeFields(props: P.ThemeProps) { - const theme = Forms.useDefined(Forms.useValid( - Settings.useGlobalSettings(props.theme), - )); +const themeOptions: Forms.MenuFieldOption<Themes.ColorSettings>[] = [ + { value: 'light', label: 'Light Theme' }, + { value: 'dark', label: 'Dark Theme' }, + { value: 'system', label: 'System Defaults' }, +]; + +function ThemeFields(): JSX.Element { + const state = Forms.useValid(Themes.useColorThemeSettings()); + return ( + <Forms.MenuField<Themes.ColorSettings> + label="Color Theme" + title="Select global color theme for the application" + state={state} + defaultValue='system' + options={themeOptions} + /> + ); +} + +// -------------------------------------------------------------------------- +// --- Editor Fields +// -------------------------------------------------------------------------- + +interface EditorFieldProps extends IvettePrefs.EditorProps { + target: string; +} + +function EditorFields(props: EditorFieldProps) { const fontsize = Forms.useValid( Settings.useGlobalSettings(props.fontSize), ); const wraptext = Forms.useValid( Settings.useGlobalSettings(props.wrapText), ); - const options = P.THEMES.map(({ id, label }) => ( - <option key={id} value={id} label={label} /> - )); const { target } = props; return ( <> - <Forms.SelectField - state={theme} - label="Theme" - title={`Set the color theme of ${target}`} - > - {options} - </Forms.SelectField> <Forms.SliderField state={fontsize} label="Font Size" @@ -85,10 +100,12 @@ function ThemeFields(props: P.ThemeProps) { ); } + + // -------------------------------------------------------------------------- // --- Editor Command Forms // -------------------------------------------------------------------------- -function EditorCommandFields(props: P.EditorCommandProps) { +function EditorCommandFields(props: IvettePrefs.EditorCommandProps) { const cmd = Forms.useDefined(Forms.useValid( Settings.useGlobalSettings(props.command), )); @@ -106,24 +123,25 @@ function EditorCommandFields(props: P.EditorCommandProps) { export default function Preferences() { return ( <Forms.Page> + <Forms.Section label="Theme" unfold> + <ThemeFields /> + </Forms.Section> <Forms.Section label="AST View" unfold> - <ThemeFields + <EditorFields target="Internal AST" - theme={P.AstTheme} - fontSize={P.AstFontSize} - wrapText={P.AstWrapText} + fontSize={IvettePrefs.AstFontSize} + wrapText={IvettePrefs.AstWrapText} /> </Forms.Section> <Forms.Section label="Source View" unfold> - <ThemeFields + <EditorFields target="Source Code" - theme={P.SourceTheme} - fontSize={P.SourceFontSize} - wrapText={P.SourceWrapText} + fontSize={IvettePrefs.SourceFontSize} + wrapText={IvettePrefs.SourceWrapText} /> </Forms.Section> <Forms.Section label="Editor Command" unfold> - <EditorCommandFields command={P.EditorCommand} /> + <EditorCommandFields command={IvettePrefs.EditorCommand} /> </Forms.Section> </Forms.Page> ); diff --git a/ivette/src/renderer/style.css b/ivette/src/renderer/style.css index fa02656e5992d03b039c13ea4f5eb9cd74e8677c..286da16776d598e6899fcd7aeae67733975d2609 100644 --- a/ivette/src/renderer/style.css +++ b/ivette/src/renderer/style.css @@ -10,7 +10,7 @@ } .component-info { - color: #555 ; + color: var(--text) ; min-width: 50px; padding-top: 4px; font-size: smaller; @@ -26,7 +26,7 @@ } .labview-stock:hover { - background: #ccc ; + background: var(--background-profound); } .labview-stock:hover * { @@ -36,19 +36,19 @@ .labview-stock.dome-dragging { border-radius: 4px ; - background: #ccc ; + background: var(--background-profound); border: thin solid black ; } .labview-content { - background: #e6e6e6 ; + background: var(--background); } .labview-titlebar { - background: #ccc ; - height: 24px ; + background: var(--background-profound); + height: 24px } .labview-handle @@ -59,11 +59,11 @@ .labview-close:hover { - fill: red ; + fill: var(--negative-button-color) ; } -.dome-window-active .labview-icon { - fill: #7d7d7d ; +.labview-icon { + fill: var(--info-text); } /* -------------------------------------------------------------------------- */