diff --git a/OpenBCI_GUI/AuditoryNeurofeedback.pde b/OpenBCI_GUI/AuditoryNeurofeedback.pde index ff180624c..04345cf6d 100644 --- a/OpenBCI_GUI/AuditoryNeurofeedback.pde +++ b/OpenBCI_GUI/AuditoryNeurofeedback.pde @@ -140,5 +140,14 @@ class AuditoryNeurofeedback { }); modeButton.setDescription("Change Auditory Feedback mode. Use the Metric to control all notes at once, or use Band Powers to control certain notes of the chord."); } + + public void updateColors() { + color btnBg = style.isDarkMode() ? style.getButtonColor() : colorNotPressed; + color btnText = style.isDarkMode() ? style.getButtonTextColor() : OPENBCI_DARKBLUE; + startStopButton.setColorBackground(btnBg); + startStopButton.getCaptionLabel().setColor(btnText); + modeButton.setColorBackground(btnBg); + modeButton.getCaptionLabel().setColor(btnText); + } } \ No newline at end of file diff --git a/OpenBCI_GUI/BoardCyton.pde b/OpenBCI_GUI/BoardCyton.pde index df2c1293f..aa3a10ea2 100644 --- a/OpenBCI_GUI/BoardCyton.pde +++ b/OpenBCI_GUI/BoardCyton.pde @@ -379,8 +379,8 @@ implements ImpedanceSettingsBoard, AccelerometerCapableBoard, AnalogCapableBoard currentADS1299Settings.values.gain[channel] = Gain.X1; currentADS1299Settings.values.inputType[channel] = InputType.NORMAL; - currentADS1299Settings.values.bias[channel] = Bias.INCLUDE; - currentADS1299Settings.values.srb2[channel] = Srb2.DISCONNECT; + currentADS1299Settings.values.bias[channel] = Bias.NO_INCLUDE; + currentADS1299Settings.values.srb2[channel] = Srb2.CONNECT; currentADS1299Settings.values.srb1[channel] = Srb1.DISCONNECT; fullCommand.append(currentADS1299Settings.getValuesString(channel, currentADS1299Settings.values)); diff --git a/OpenBCI_GUI/ChannelSelect.pde b/OpenBCI_GUI/ChannelSelect.pde index 963101cfd..d4e3514bb 100644 --- a/OpenBCI_GUI/ChannelSelect.pde +++ b/OpenBCI_GUI/ChannelSelect.pde @@ -49,7 +49,7 @@ class ChannelSelect { if (channelSelectHover) { fill(OPENBCI_BLUE); } else { - fill(OPENBCI_DARKBLUE); + fill(style.isDarkMode() ? style.getTextColor() : OPENBCI_DARKBLUE); } textFont(p5, 12); @@ -100,7 +100,7 @@ class ChannelSelect { public void drawGrayBackground(int _x, int _y, int _w, int _h) { pushStyle(); - fill(200); + fill(style.isDarkMode() ? style.getBoxColor() : 200); rect(_x, _y, _w, _h); popStyle(); } diff --git a/OpenBCI_GUI/ControlPanel.pde b/OpenBCI_GUI/ControlPanel.pde index e50e9ea1e..601191b3f 100644 --- a/OpenBCI_GUI/ControlPanel.pde +++ b/OpenBCI_GUI/ControlPanel.pde @@ -227,14 +227,14 @@ class ControlPanel { //draw the box that tells you to stop the system in order to edit control settings if (drawStopInstructions) { pushStyle(); - fill(boxColor); + fill(style.getBoxColor()); strokeWeight(1); - stroke(boxStrokeColor); + stroke(style.getBoxStrokeColor()); rect(x, y, w, dataSourceBox.h); //draw background of box String stopInstructions = "Press the \"STOP SESSION\" button to change your data source or edit system settings."; textAlign(CENTER, TOP); textFont(p4, 14); - fill(OPENBCI_DARKBLUE); + fill(style.getTextColor()); text(stopInstructions, x + globalPadding*2, y + globalPadding*3, w - globalPadding*4, dataSourceBox.h - globalPadding*4); popStyle(); } diff --git a/OpenBCI_GUI/Debugging.pde b/OpenBCI_GUI/Debugging.pde index ecf144322..1f67a9fe9 100644 --- a/OpenBCI_GUI/Debugging.pde +++ b/OpenBCI_GUI/Debugging.pde @@ -75,56 +75,48 @@ class HelpWidget { pushStyle(); - if (colorScheme == COLOR_SCHEME_DEFAULT) { - // draw background of widget - stroke(OPENBCI_DARKBLUE); - fill(255); - rect(-1, height-h, width+2, h); - noStroke(); - - //draw bg of text field of widget - strokeWeight(1); - stroke(color(0, 5, 11)); - fill(color(0, 5, 11)); - rect(x + padding, height-h + padding, width - padding*2, h - padding *2); - - textFont(p4); - textSize(14); - fill(255); - textAlign(LEFT, TOP); - text(currentOutput, padding*2, height - h + padding); - } else if (colorScheme == COLOR_SCHEME_ALTERNATIVE_A){ - // draw background of widget - stroke(OPENBCI_DARKBLUE); - fill(OPENBCI_BLUE); - rect(-1, height-h, width+2, h); - noStroke(); - - //draw bg of text field of widget - strokeWeight(1); - int saturationFadeValue = 0; - if (outputWasTriggered) { - int timeDelta = millis() - colorFadeCounter; - saturationFadeValue = (int)map(timeDelta, 0, colorFadeTimeMillis, 100, 0); - if (timeDelta > colorFadeTimeMillis) { - outputWasTriggered = false; - } + // Use the global style manager for theme-aware colors + color helpBg = style.getHelpWidgetBackground(); + color helpTextBg = style.getHelpWidgetTextBackground(); + color helpStroke = style.getBoxStrokeColor(); + + // draw background of widget + stroke(helpStroke); + fill(helpBg); + rect(-1, height-h, width+2, h); + noStroke(); + + //draw bg of text field of widget + strokeWeight(1); + int saturationFadeValue = 0; + if (outputWasTriggered) { + int timeDelta = millis() - colorFadeCounter; + saturationFadeValue = (int)map(timeDelta, 0, colorFadeTimeMillis, 100, 0); + if (timeDelta > colorFadeTimeMillis) { + outputWasTriggered = false; } + } + + // For dark mode, use solid colors; for other modes, use HSB fade effect + if (style.isDarkMode()) { + stroke(helpTextBg); + fill(helpTextBg); + } else { //Colors in this method are calculated using Hue, Saturation, Brightness colorMode(HSB, 360, 100, 100); color c = getBackgroundColor(saturationFadeValue); stroke(c); fill(c); - rect(x + padding, height-h + padding, width - padding*2, h - padding *2); - - // Revert color mode back to standard RGB here - colorMode(RGB, 255, 255, 255); - textFont(p4); - textSize(14); - fill(getTextColor()); - textAlign(LEFT, TOP); - text(currentOutput, padding*2, height - h + padding); } + rect(x + padding, height-h + padding, width - padding*2, h - padding *2); + + // Revert color mode back to standard RGB here + colorMode(RGB, 255, 255, 255); + textFont(p4); + textSize(14); + fill(style.isDarkMode() ? style.getTextColor() : getTextColor()); + textAlign(LEFT, TOP); + text(currentOutput, padding*2, height - h + padding); popStyle(); } diff --git a/OpenBCI_GUI/FilterUI.pde b/OpenBCI_GUI/FilterUI.pde index 2e1c86b26..ae32e80af 100644 --- a/OpenBCI_GUI/FilterUI.pde +++ b/OpenBCI_GUI/FilterUI.pde @@ -36,7 +36,9 @@ class FilterUIPopup extends PApplet implements Runnable { private color headerColor = OPENBCI_BLUE; private color buttonColor = OPENBCI_BLUE; + // backgroundColor will be set dynamically based on theme private color backgroundColor = GREY_235; + private color textLabelColor = color(102); private ControlP5 cp5; @@ -137,12 +139,25 @@ class FilterUIPopup extends PApplet implements Runnable { frame.toFront(); frame.requestFocus(); + // Set theme-aware colors + updateThemeColors(); + cp5 = new ControlP5(this); cp5.setGraphics(this, 0, 0); cp5.setAutoDraw(false); createAllCp5Objects(); } + + private void updateThemeColors() { + if (style.isDarkMode()) { + backgroundColor = style.getBoxColor(); + textLabelColor = style.getTextColor(); + } else { + backgroundColor = GREY_235; + textLabelColor = color(102); + } + } @Override void draw() { @@ -220,7 +235,7 @@ class FilterUIPopup extends PApplet implements Runnable { text("Notch", headerObjX[2], HEADER_OBJ_Y, HEADER_OBJ_WIDTH, uiObjectHeight); // Column labels textAlign(CENTER, TOP); - fill(102); + fill(textLabelColor); text("Channel", columnObjX[0], HEADER_HEIGHT + SM_SPACER, TEXTFIELD_WIDTH, HEADER_HEIGHT); String firstColumnHeader = ""; String secondColumnHeader = ""; diff --git a/OpenBCI_GUI/Grid.pde b/OpenBCI_GUI/Grid.pde index 69a996a52..9fff263b1 100644 --- a/OpenBCI_GUI/Grid.pde +++ b/OpenBCI_GUI/Grid.pde @@ -32,7 +32,12 @@ class Grid { strings = new String[numRows][numCols]; textColors = new color[numRows][numCols]; - color defaultTextColor = OPENBCI_DARKBLUE; + updateDefaultTextColor(); + } + + // Update default text color based on theme + public void updateDefaultTextColor() { + color defaultTextColor = style.isDarkMode() ? style.getTextColor() : OPENBCI_DARKBLUE; for (color[] row: textColors) { Arrays.fill(row, defaultTextColor); } @@ -41,7 +46,7 @@ class Grid { public void draw() { pushStyle(); textAlign(LEFT); - stroke(OPENBCI_DARKBLUE); + stroke(style.isDarkMode() ? style.getGraphGridColor() : OPENBCI_DARKBLUE); textFont(tableFont, tableFontSize); if (drawTableInnerLines) { @@ -69,7 +74,7 @@ class Grid { if (drawTableBorder) { noFill(); - stroke(OPENBCI_DARKBLUE); + stroke(style.isDarkMode() ? style.getGraphGridColor() : OPENBCI_DARKBLUE); rect(x, y, w, rowOffset[numRows - 1]); } diff --git a/OpenBCI_GUI/GuiSettings.pde b/OpenBCI_GUI/GuiSettings.pde index c492433c1..80b4a1f76 100644 --- a/OpenBCI_GUI/GuiSettings.pde +++ b/OpenBCI_GUI/GuiSettings.pde @@ -40,6 +40,7 @@ public class GuiSettingsValues { public boolean autoStartDataStream = false; public boolean autoStartNetworkStream = false; public boolean autoLoadSessionSettings = false; + public ThemeType themeType = ThemeType.DEFAULT; public GuiSettingsValues() { } @@ -87,6 +88,8 @@ class GuiSettings { saveToFile(); } + // Apply persisted theme + applyThemeFromSettings(); return true; } catch (IOException e) { @@ -168,6 +171,7 @@ class GuiSettings { topNav.configSelector.toggleAutoStartDataStreamFrontEnd(getAutoStartDataStream()); topNav.configSelector.toggleAutoStartNetworkStreamFrontEnd(getAutoStartNetworkStream()); topNav.configSelector.toggleAutoLoadSessionSettingsFrontEnd(getAutoLoadSessionSettings()); + applyThemeFromSettings(); } public void setExpertMode(ExpertModeEnum val) { @@ -241,4 +245,25 @@ class GuiSettings { values.autoLoadSessionSettings = b; saveToFile(); } + + public void setThemeType(ThemeType themeType) { + values.themeType = themeType; + saveToFile(); + println("OpenBCI_GUI::Settings: Theme saved as " + themeType.getString()); + } + + public ThemeType getThemeType() { + return values.themeType; + } + + private void applyThemeFromSettings() { + if (style != null && values.themeType != null) { + style.setTheme(values.themeType); + // If widgets are initialized, notify them of the theme + if (widgetManager != null) { + style.notifyThemeChange(); + } + println("OpenBCI_GUI::Settings: Applied persisted theme: " + values.themeType.getString()); + } + } } \ No newline at end of file diff --git a/OpenBCI_GUI/Interactivity.pde b/OpenBCI_GUI/Interactivity.pde index eba496f1c..045db0d0a 100644 --- a/OpenBCI_GUI/Interactivity.pde +++ b/OpenBCI_GUI/Interactivity.pde @@ -58,15 +58,9 @@ void parseKey(char val) { drawContainers = !drawContainers; return; case '{': - /* - if(colorScheme == COLOR_SCHEME_DEFAULT){ - colorScheme = COLOR_SCHEME_ALTERNATIVE_A; - } else if(colorScheme == COLOR_SCHEME_ALTERNATIVE_A) { - colorScheme = COLOR_SCHEME_DEFAULT; - } - */ - //topNav.updateNavButtonsBasedOnColorScheme(); - output("New Dark color scheme coming soon!"); + // Cycle through themes: Default -> Dark -> Light -> Default... + style.cycleTheme(); + output("Theme changed to: " + style.getThemeName()); return; //deactivate channels 1-4 diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde index b6cb0cbc1..2c0228709 100644 --- a/OpenBCI_GUI/OpenBCI_GUI.pde +++ b/OpenBCI_GUI/OpenBCI_GUI.pde @@ -402,6 +402,11 @@ void setup() { directoryManager = new DirectoryManager(); + // Initialize the global style/theme manager + // This must be done early so all UI components can access theme colors + style = new Style(); + println("Style: Initialized with " + style.getThemeName() + " theme"); + // redirect all output to a custom stream that will intercept all prints // write them to file and display them in the GUI's console window outputStream = new CustomOutputStream(System.out); diff --git a/OpenBCI_GUI/Style.pde b/OpenBCI_GUI/Style.pde new file mode 100644 index 000000000..445d67439 --- /dev/null +++ b/OpenBCI_GUI/Style.pde @@ -0,0 +1,450 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// +// Style.pde - Theme Manager for OpenBCI GUI +// +// Created for GUI v7 Dark Mode Feature (Issue #705) +// Based on PR #1063 (Andrey1994) and PR #1248 (retiutut) architecture. +// This class centralizes all color definitions and allows switching between themes. +// +// Themes available: +// - DEFAULT: Original OpenBCI blue theme (Legacy) +// - LIGHT: Light/soft theme with reduced contrast +// - DARK: Dark mode for accessibility (reduced eye strain) +// +/////////////////////////////////////////////////////////////////////////////////////// + +/** + * Enum to store all possible themes. + * Based on PR #1063 ThemeType pattern. + */ +public enum ThemeType implements IndexingInterface { + DEFAULT(0, "Default"), + LIGHT(1, "Light"), + DARK(2, "Dark"); + + private int index; + private String label; + + ThemeType(int index, String label) { + this.index = index; + this.label = label; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public ThemeType next() { + ThemeType[] vals = values(); + return vals[(ordinal() + 1) % vals.length]; + } +} + +// Global style instance - use this throughout the GUI +Style style; + +/** + * Style class manages all GUI colors based on the selected theme. + * + * HOW TO USE: + * Instead of using hardcoded colors like: + * fill(color(200)); // old way + * + * Use the style methods: + * fill(style.getBoxColor()); // new way + * + * This allows colors to change automatically when the theme changes. + */ +class Style { + + // Current active theme + private ThemeType currentTheme; + + // ============================================================ + // COLOR DEFINITIONS FOR EACH THEME + // ============================================================ + + // --- DEFAULT Theme (Original OpenBCI Blue / Legacy) --- + private final color DEFAULT_TOPNAV_BG = color(31, 69, 110); // OPENBCI_BLUE + private final color DEFAULT_SUBNAV_BG = color(57, 128, 204); // buttonsLightBlue + private final color DEFAULT_BOX_BG = color(200); // boxColor + private final color DEFAULT_BOX_STROKE = color(1, 18, 41); // OPENBCI_DARKBLUE + private final color DEFAULT_WIDGET_BG = color(255); // WHITE + private final color DEFAULT_TEXT_PRIMARY = color(1, 18, 41); // OPENBCI_DARKBLUE + private final color DEFAULT_TEXT_SECONDARY = color(100); // GREY_100 + private final color DEFAULT_TEXT_ON_DARK = color(255); // WHITE + private final color DEFAULT_BUTTON_BG = color(57, 128, 204); // buttonsLightBlue + private final color DEFAULT_BUTTON_TEXT = color(255); // WHITE + private final color DEFAULT_BUTTON_HOVER = color(177, 184, 193); + private final color DEFAULT_BUTTON_PRESSED = color(150, 170, 200); + private final color DEFAULT_HELP_WIDGET_BG = color(31, 69, 110); // OPENBCI_BLUE + private final color DEFAULT_HELP_WIDGET_TEXT_BG = color(1, 18, 41); + + // --- LIGHT Theme (Soft/reduced contrast) --- + private final color LIGHT_TOPNAV_BG = color(55, 95, 140); // Softer OpenBCI blue + private final color LIGHT_SUBNAV_BG = color(80, 145, 215); // Lighter blue + private final color LIGHT_BOX_BG = color(227, 230, 232); // #E3E6E8 soft grey + private final color LIGHT_BOX_STROKE = color(193, 200, 205); // #C1C8CD light grey border + private final color LIGHT_WIDGET_BG = color(240, 242, 245); // Near-white with slight warmth + private final color LIGHT_TEXT_PRIMARY = color(50, 60, 70); // Dark grey (not harsh black) + private final color LIGHT_TEXT_SECONDARY = color(120, 131, 140); // #78838C muted grey + private final color LIGHT_TEXT_ON_DARK = color(255); // WHITE + private final color LIGHT_BUTTON_BG = color(0, 163, 221); // #00A3DD cyan-blue + private final color LIGHT_BUTTON_TEXT = color(255); // WHITE + private final color LIGHT_BUTTON_HOVER = color(255, 148, 68); // #FF9444 orange hover + private final color LIGHT_BUTTON_PRESSED = color(249, 128, 37); // #F98025 orange pressed + private final color LIGHT_HELP_WIDGET_BG = color(55, 95, 140); // Match topnav + private final color LIGHT_HELP_WIDGET_TEXT_BG = color(30, 55, 85); + + // --- DARK Theme (Dark Mode - OpenBCI colors but darker, near black) --- + private final color DARK_TOPNAV_BG = color(8, 18, 28); // Very dark OpenBCI blue (near black) + private final color DARK_SUBNAV_BG = color(12, 28, 45); // Slightly lighter dark blue + private final color DARK_BOX_BG = color(18, 35, 55); // Dark blue-grey for boxes + private final color DARK_BOX_STROKE = color(25, 50, 80); // Subtle blue border + private final color DARK_WIDGET_BG = color(10, 22, 35); // Very dark blue widget background + private final color DARK_TEXT_PRIMARY = color(200, 210, 220); // Soft off-white text (not harsh) + private final color DARK_TEXT_SECONDARY = color(140, 155, 170); // Muted blue-grey text + private final color DARK_TEXT_ON_DARK = color(200, 210, 220); // Same as primary in dark + private final color DARK_BUTTON_BG = color(20, 45, 75); // Dark OpenBCI blue button + private final color DARK_BUTTON_TEXT = color(200, 210, 220); // Soft light text on buttons + private final color DARK_BUTTON_HOVER = color(28, 58, 95); // Slightly lighter on hover + private final color DARK_BUTTON_PRESSED = color(15, 35, 60); // Darker when pressed + private final color DARK_HELP_WIDGET_BG = color(8, 18, 28); // Match topnav + private final color DARK_HELP_WIDGET_TEXT_BG = color(5, 12, 20); // Near black + + // --- Dimmed accent colors for dark mode (reduced contrast) --- + private final color DARK_SUCCESS = color(140, 190, 130); // Dimmed green + private final color DARK_ERROR = color(180, 70, 60); // Dimmed red + private final color DARK_WARNING = color(180, 145, 40); // Dimmed yellow + private final color DARK_ACCENT = color(45, 100, 160); // Dimmed OpenBCI blue + + // ============================================================ + // CONSTRUCTOR + // ============================================================ + + Style() { + this.currentTheme = ThemeType.DEFAULT; + } + + Style(ThemeType theme) { + this.currentTheme = theme; + } + + // ============================================================ + // THEME SWITCHING + // ============================================================ + + /** + * Set the current theme + * @param theme ThemeType enum value + */ + void setTheme(ThemeType theme) { + this.currentTheme = theme; + println("Style: Theme changed to " + getThemeName()); + } + + /** + * Get the current ThemeType + */ + ThemeType getThemeType() { + return this.currentTheme; + } + + /** + * Get the current theme name as a string + */ + String getThemeName() { + return currentTheme.getString(); + } + + /** + * Cycle to the next theme and persist the choice + */ + void cycleTheme() { + currentTheme = currentTheme.next(); + println("Style: Theme cycled to " + getThemeName()); + // Persist the theme choice + if (guiSettings != null) { + guiSettings.setThemeType(currentTheme); + } + notifyThemeChange(); + } + + /** + * Notify all widgets that the theme has changed so they can update their colors + */ + void notifyThemeChange() { + // Update global dropdown colors + updateDropdownColors(); + + // Update all widget plot colors when theme changes + if (widgetManager != null) { + widgetManager.updateAllWidgetColors(); + } + } + + /** + * Update the global dropdown colors based on current theme + */ + void updateDropdownColors() { + switch(currentTheme) { + case DARK: + dropdownColorsGlobal.setActive((int)color(15, 35, 60)); + dropdownColorsGlobal.setForeground((int)color(25, 50, 80)); + dropdownColorsGlobal.setBackground((int)color(18, 35, 55)); + dropdownColorsGlobal.setCaptionLabel((int)color(200, 210, 220)); + dropdownColorsGlobal.setValueLabel((int)color(180, 190, 200)); + break; + case LIGHT: + dropdownColorsGlobal.setActive((int)LIGHT_BUTTON_PRESSED); + dropdownColorsGlobal.setForeground((int)LIGHT_BUTTON_HOVER); + dropdownColorsGlobal.setBackground((int)color(227, 230, 232)); + dropdownColorsGlobal.setCaptionLabel((int)color(50, 60, 70)); + dropdownColorsGlobal.setValueLabel((int)color(120, 131, 140)); + break; + default: // DEFAULT + dropdownColorsGlobal.setActive((int)BUTTON_PRESSED); + dropdownColorsGlobal.setForeground((int)BUTTON_HOVER); + dropdownColorsGlobal.setBackground((int)color(255)); + dropdownColorsGlobal.setCaptionLabel((int)color(1, 18, 41)); + dropdownColorsGlobal.setValueLabel((int)color(100)); + break; + } + } + + /** + * Check if dark mode is active + */ + boolean isDarkMode() { + return currentTheme == ThemeType.DARK; + } + + /** + * Check if light mode is active + */ + boolean isLightMode() { + return currentTheme == ThemeType.LIGHT; + } + + /** + * Check if default mode is active + */ + boolean isDefaultMode() { + return currentTheme == ThemeType.DEFAULT; + } + + // ============================================================ + // COLOR GETTER METHODS + // These return the appropriate color based on current theme + // ============================================================ + + // --- Navigation Colors --- + + color getTopNavBackground() { + switch(currentTheme) { + case DARK: return DARK_TOPNAV_BG; + case LIGHT: return LIGHT_TOPNAV_BG; + default: return DEFAULT_TOPNAV_BG; + } + } + + color getSubNavBackground() { + switch(currentTheme) { + case DARK: return DARK_SUBNAV_BG; + case LIGHT: return LIGHT_SUBNAV_BG; + default: return DEFAULT_SUBNAV_BG; + } + } + + // --- Box/Panel Colors --- + + color getBoxColor() { + switch(currentTheme) { + case DARK: return DARK_BOX_BG; + case LIGHT: return LIGHT_BOX_BG; + default: return DEFAULT_BOX_BG; + } + } + + color getBoxStrokeColor() { + switch(currentTheme) { + case DARK: return DARK_BOX_STROKE; + case LIGHT: return LIGHT_BOX_STROKE; + default: return DEFAULT_BOX_STROKE; + } + } + + // --- Widget Colors --- + + color getWidgetBackground() { + switch(currentTheme) { + case DARK: return DARK_WIDGET_BG; + case LIGHT: return LIGHT_WIDGET_BG; + default: return DEFAULT_WIDGET_BG; + } + } + + // --- Text Colors --- + + color getTextColor() { + switch(currentTheme) { + case DARK: return DARK_TEXT_PRIMARY; + case LIGHT: return LIGHT_TEXT_PRIMARY; + default: return DEFAULT_TEXT_PRIMARY; + } + } + + color getSecondaryTextColor() { + switch(currentTheme) { + case DARK: return DARK_TEXT_SECONDARY; + case LIGHT: return LIGHT_TEXT_SECONDARY; + default: return DEFAULT_TEXT_SECONDARY; + } + } + + color getTextOnDarkBackground() { + switch(currentTheme) { + case DARK: return DARK_TEXT_ON_DARK; + case LIGHT: return LIGHT_TEXT_ON_DARK; + default: return DEFAULT_TEXT_ON_DARK; + } + } + + // --- Button Colors --- + + color getButtonColor() { + switch(currentTheme) { + case DARK: return DARK_BUTTON_BG; + case LIGHT: return LIGHT_BUTTON_BG; + default: return DEFAULT_BUTTON_BG; + } + } + + color getButtonTextColor() { + switch(currentTheme) { + case DARK: return DARK_BUTTON_TEXT; + case LIGHT: return LIGHT_BUTTON_TEXT; + default: return DEFAULT_BUTTON_TEXT; + } + } + + color getButtonHoverColor() { + switch(currentTheme) { + case DARK: return DARK_BUTTON_HOVER; + case LIGHT: return LIGHT_BUTTON_HOVER; + default: return DEFAULT_BUTTON_HOVER; + } + } + + color getButtonPressedColor() { + switch(currentTheme) { + case DARK: return DARK_BUTTON_PRESSED; + case LIGHT: return LIGHT_BUTTON_PRESSED; + default: return DEFAULT_BUTTON_PRESSED; + } + } + + // --- Help Widget (Console) Colors --- + + color getHelpWidgetBackground() { + switch(currentTheme) { + case DARK: return DARK_HELP_WIDGET_BG; + case LIGHT: return LIGHT_HELP_WIDGET_BG; + default: return DEFAULT_HELP_WIDGET_BG; + } + } + + color getHelpWidgetTextBackground() { + switch(currentTheme) { + case DARK: return DARK_HELP_WIDGET_TEXT_BG; + case LIGHT: return LIGHT_HELP_WIDGET_TEXT_BG; + default: return DEFAULT_HELP_WIDGET_TEXT_BG; + } + } + + // --- Graph/Plot Colors --- + + color getGraphBackground() { + switch(currentTheme) { + case DARK: return DARK_WIDGET_BG; + case LIGHT: return color(240, 242, 245); + default: return color(255); + } + } + + color getGraphBoxBackground() { + switch(currentTheme) { + case DARK: return color(12, 25, 40); + case LIGHT: return color(250, 251, 252); + default: return color(245); + } + } + + color getGraphGridColor() { + switch(currentTheme) { + case DARK: return color(35, 55, 80); + case LIGHT: return color(220, 225, 230); + default: return color(210); + } + } + + color getGraphLineColor() { + switch(currentTheme) { + case DARK: return color(45, 70, 100); + case LIGHT: return color(200, 208, 215); + default: return color(210); + } + } + + color getGraphAxisColor() { + switch(currentTheme) { + case DARK: return color(160, 175, 190); + case LIGHT: return color(80, 95, 110); + default: return OPENBCI_DARKBLUE; + } + } + + // --- Special Colors (dimmed in dark mode for reduced contrast) --- + + color getSuccessColor() { + // Green - dimmed in dark mode + return isDarkMode() ? DARK_SUCCESS : color(195, 242, 181); + } + + color getErrorColor() { + // Red - dimmed in dark mode + return isDarkMode() ? DARK_ERROR : color(224, 56, 45); + } + + color getWarningColor() { + // Yellow - dimmed in dark mode + return isDarkMode() ? DARK_WARNING : color(221, 178, 13); + } + + color getAccentColor() { + // OpenBCI blue accent - dimmed in dark mode + return isDarkMode() ? DARK_ACCENT : color(57, 128, 204); + } + + // ============================================================ + // LOGO SELECTION + // Returns appropriate logo based on theme + // ============================================================ + + /** + * Get the appropriate logo for the current theme + * @param whiteLogo The white version of the logo + * @param blackLogo The black version of the logo + * @return The logo that contrasts best with current theme + */ + PImage getLogo(PImage whiteLogo, PImage blackLogo) { + // White logo works for both Default (blue bg) and Dark (dark bg) + return whiteLogo; + } +} diff --git a/OpenBCI_GUI/TimeSeriesWidgetHelperClasses.pde b/OpenBCI_GUI/TimeSeriesWidgetHelperClasses.pde index 994cba5c4..02aad352e 100644 --- a/OpenBCI_GUI/TimeSeriesWidgetHelperClasses.pde +++ b/OpenBCI_GUI/TimeSeriesWidgetHelperClasses.pde @@ -75,9 +75,8 @@ class ChannelBar { plot.setPointSize(2); plot.setPointColor(0); plot.setAllFontProperties("Arial", 0, 14); - plot.getXAxis().setFontColor(OPENBCI_DARKBLUE); - plot.getXAxis().setLineColor(OPENBCI_DARKBLUE); - plot.getXAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); + // Apply theme colors to plot + updatePlotColors(); if (channelIndex == globalChannelCount-1) { plot.getXAxis().setAxisLabelText("Time (s)"); plot.getXAxis().getAxisLabel().setOffset(plotBottomWellH/2 + 5f); @@ -348,6 +347,18 @@ class ChannelBar { boolean isLastChannel = channelIndex == widgetManager.getTimeSeriesWidget().tsChanSelect.getActiveChannels().get(numActiveChannels - 1); return isLastChannel; } + + public void updatePlotColors() { + // Apply theme colors to the plot + color axisColor = style.getGraphAxisColor(); + plot.setBgColor(style.getGraphBackground()); + plot.setBoxBgColor(style.getGraphBoxBackground()); + plot.setBoxLineColor(style.getGraphLineColor()); + plot.setGridLineColor(style.getGraphGridColor()); + plot.getXAxis().setFontColor(axisColor); + plot.getXAxis().setLineColor(axisColor); + plot.getXAxis().getAxisLabel().setFontColor(axisColor); + } public void mousePressed() { } @@ -605,16 +616,16 @@ class PlaybackScrollbar { //select color for playback indicator if (over || locked) { - fill(OPENBCI_DARKBLUE); + fill(style.isDarkMode() ? style.getTextColor() : OPENBCI_DARKBLUE); } else { - fill(102, 102, 102); + fill(style.isDarkMode() ? style.getSecondaryTextColor() : color(102, 102, 102)); } //draws playback position indicator rect(spos, ypos, sheight/2, sheight); //draw current timestamp and X of Y Seconds above scrollbar textFont(p2, 18); - fill(OPENBCI_DARKBLUE); + fill(style.isDarkMode() ? style.getTextColor() : OPENBCI_DARKBLUE); textAlign(LEFT, TOP); float textHeight = textAscent() - textDescent(); float textY = y - textHeight - 10; @@ -695,7 +706,7 @@ class TimeDisplay { if (!currentAbsoluteTimeToDisplay.equals(null)) { int fontSize = 17; textFont(p2, fontSize); - fill(OPENBCI_DARKBLUE); + fill(style.isDarkMode() ? style.getTextColor() : OPENBCI_DARKBLUE); float tw = textWidth(currentAbsoluteTimeToDisplay); text(currentAbsoluteTimeToDisplay, xpos + swidth - tw, ypos); text(streamTimeElapsed.toString(), xpos + 10, ypos); diff --git a/OpenBCI_GUI/TopNav.pde b/OpenBCI_GUI/TopNav.pde index 36be7ca7d..416e02a3c 100644 --- a/OpenBCI_GUI/TopNav.pde +++ b/OpenBCI_GUI/TopNav.pde @@ -197,29 +197,19 @@ class TopNav { } void draw() { - PImage logo; - int logo_w = 128; - int logo_h = 22; - color topNavBg; - color subNavBg; + // Use the global style manager for theme-aware colors + PImage logo = style.getLogo(logo_white, logo_black); + color topNavBg = style.getTopNavBackground(); + color subNavBg = style.getSubNavBackground(); - if (colorScheme == COLOR_SCHEME_ALTERNATIVE_A) { - topNavBg = OPENBCI_BLUE; - subNavBg = SUBNAV_LIGHTBLUE; - logo = logo_white; - } else { - topNavBg = WHITE; - subNavBg = color(229); - logo = logo_black; - } + // Update stroke color based on theme + color currentStrokeColor = style.isDarkMode() ? style.getBoxStrokeColor() : strokeColor; //Draw background rectangles for TopNav and SubNav pushStyle(); - //stroke(OPENBCI_DARKBLUE); fill(topNavBg); rect(0, 0, width, navBarHeight); - //noStroke(); - stroke(strokeColor); + stroke(currentStrokeColor); fill(subNavBg); rect(-1, navBarHeight, width+2, navBarHeight); popStyle(); @@ -257,9 +247,8 @@ class TopNav { configSelector.draw(); tutorialSelector.draw(); - //Draw Console Log Image on top of cp5 object - PImage _logo = (colorScheme == COLOR_SCHEME_DEFAULT) ? consoleImgBlue : consoleImgWhite; - image(_logo, debugButton.getPosition()[0] + 6, debugButton.getPosition()[1] + 2, 22, 22); + //Draw Console Log Image on top of cp5 object - white for both themes (dark bg) + image(consoleImgWhite, debugButton.getPosition()[0] + 6, debugButton.getPosition()[1] + 2, 22, 22); //Draw camera image on top of cp5 object image(screenshotImgWhite, screenshotButton.getPosition()[0] + 6, screenshotButton.getPosition()[1] + 2, 22, 22); } @@ -730,6 +719,7 @@ class ConfigSelector { private boolean clearAllSettingsPressed; public boolean isVisible; private ControlP5 settings_cp5; + private Button themeToggle; private Button expertMode; private Button autoStartDataStream; private Button autoStartNetworkStream; @@ -755,7 +745,7 @@ class ConfigSelector { margin = 6; b_w = w - margin*2; b_h = 22; - h = margin*10 + b_h*9; + h = margin*11 + b_h*10; //makes the setting text "are you sure" display correctly on linux osPadding = isLinux() ? -3 : -2; osPadding2 = isLinux() ? 5 : 0; @@ -768,6 +758,8 @@ class ConfigSelector { isVisible = false; int buttonNumber = 0; + createThemeToggleButton("themeToggle", getThemeButtonText(), x + margin, y + margin*(buttonNumber+1) + b_h*(buttonNumber), b_w, b_h); + buttonNumber++; createExpertModeButton("expertMode", "Turn Expert Mode On", x + margin, y + margin*(buttonNumber+1) + b_h*(buttonNumber), b_w, b_h); buttonNumber++; createAutoStartDataStreamButton("autoStartDataStream", "Auto Start Data Stream", x + margin, y + margin*(buttonNumber+1) + b_h*(buttonNumber), b_w, b_h); @@ -791,8 +783,9 @@ class ConfigSelector { public void draw() { if (isVisible) { //only draw if visible - color strokeColor = OPENBCI_DARKBLUE; - color fillColor = SUBNAV_LIGHTBLUE; + // Use theme-aware colors + color strokeColor = style.isDarkMode() ? style.getBoxStrokeColor() : OPENBCI_DARKBLUE; + color fillColor = style.isDarkMode() ? style.getBoxColor() : SUBNAV_LIGHTBLUE; pushStyle(); @@ -807,7 +800,7 @@ class ConfigSelector { if (clearAllSettingsPressed) { textFont(p2, 16); - fill(255); + fill(style.isDarkMode() ? style.getTextColor() : 255); textAlign(CENTER); text("Are You Sure?", x + w/2, clearAllGUISettings.getPosition()[1] + b_h*2); } @@ -847,7 +840,7 @@ class ConfigSelector { x = width - w - 3; int dx = oldX - x; - h = !isSessionStarted ? margin*6 + b_h*5 : margin*9 + b_h*8; + h = !isSessionStarted ? margin*7 + b_h*6 : margin*10 + b_h*9; //Update the Y position for the clear settings buttons float clearSettingsButtonY = !isSessionStarted ? @@ -932,6 +925,22 @@ class ConfigSelector { }); } + private void createThemeToggleButton(String name, String text, int _x, int _y, int _w, int _h) { + themeToggle = createButton(settings_cp5, name, text, _x, _y, _w, _h, p5, 12, BUTTON_EXPERTPURPLE, WHITE); + themeToggle.onRelease(new CallbackListener() { + public void controlEvent(CallbackEvent theEvent) { + style.cycleTheme(); + themeToggle.getCaptionLabel().setText(getThemeButtonText()); + output("Theme changed to: " + style.getThemeName()); + } + }); + themeToggle.setDescription("Toggle between Default, Dark, and Light themes for the GUI."); + } + + private String getThemeButtonText() { + return "Theme: " + style.getThemeName(); + } + private void createExpertModeButton(String name, String text, int _x, int _y, int _w, int _h) { expertMode = createButton(settings_cp5, name, text, _x, _y, _w, _h, p5, 12, BUTTON_NOOBGREEN, WHITE); expertMode.onRelease(new CallbackListener() { diff --git a/OpenBCI_GUI/W_Accelerometer.pde b/OpenBCI_GUI/W_Accelerometer.pde index 1fc78c17d..0967654a7 100644 --- a/OpenBCI_GUI/W_Accelerometer.pde +++ b/OpenBCI_GUI/W_Accelerometer.pde @@ -122,18 +122,20 @@ class W_Accelerometer extends WidgetWithSettings { pushStyle(); - fill(50); + // Use theme-aware colors for labels + fill(style.isDarkMode() ? style.getSecondaryTextColor() : 50); textFont(p4, 14); textAlign(CENTER,CENTER); text("z", polarWindowX, (polarWindowY-polarWindowHeight/2)-12); text("x", (polarWindowX+polarWindowWidth/2)+8, polarWindowY-5); text("y", (polarWindowX+polarCorner)+10, (polarWindowY-polarCorner)-10); - fill(graphBG); //pulse window background - stroke(graphStroke); + // Use theme-aware colors for circular display + fill(style.isDarkMode() ? style.getGraphBoxBackground() : graphBG); + stroke(style.isDarkMode() ? style.getGraphLineColor() : graphStroke); ellipse(polarWindowX,polarWindowY,polarWindowWidth,polarWindowHeight); - stroke(180); + stroke(style.isDarkMode() ? style.getGraphGridColor() : 180); line(polarWindowX-polarWindowWidth/2, polarWindowY, polarWindowX+polarWindowWidth/2, polarWindowY); line(polarWindowX, polarWindowY-polarWindowHeight/2, polarWindowX, polarWindowY+polarWindowHeight/2); line(polarWindowX-polarCorner, polarWindowY+polarCorner, polarWindowX+polarCorner, polarWindowY-polarCorner); @@ -176,6 +178,12 @@ class W_Accelerometer extends WidgetWithSettings { accelModeButton.setPosition((int)(x0 + 1), (int)(y0 + NAV_HEIGHT + 1)); } + @Override + public void updateColors() { + super.updateColors(); // Update dropdown colors + accelerometerBar.updatePlotColors(); + } + void mousePressed() { super.mousePressed(); } @@ -352,12 +360,8 @@ class AccelerometerBar { plot.setAllFontProperties("Arial", 0, 14); plot.getXAxis().getAxisLabel().setOffset(float(accBarPadding)); plot.getYAxis().getAxisLabel().setOffset(float(accBarPadding)); - plot.getXAxis().setFontColor(OPENBCI_DARKBLUE); - plot.getXAxis().setLineColor(OPENBCI_DARKBLUE); - plot.getXAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); - plot.getYAxis().setFontColor(OPENBCI_DARKBLUE); - plot.getYAxis().setLineColor(OPENBCI_DARKBLUE); - plot.getYAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); + // Apply theme colors + updatePlotColors(); gplotAutoscaler = new GPlotAutoscaler(false, AUTOSCALE_SPACING); initArrays(); @@ -474,4 +478,19 @@ class AccelerometerBar { plot.setDim(w - 36 - 4, h); } + + void updatePlotColors() { + // Apply theme colors to the accelerometer plot + color axisColor = style.getGraphAxisColor(); + plot.setBgColor(style.getGraphBackground()); + plot.setBoxBgColor(style.getGraphBoxBackground()); + plot.setBoxLineColor(style.getGraphLineColor()); + plot.setGridLineColor(style.getGraphGridColor()); + plot.getXAxis().setFontColor(axisColor); + plot.getXAxis().setLineColor(axisColor); + plot.getXAxis().getAxisLabel().setFontColor(axisColor); + plot.getYAxis().setFontColor(axisColor); + plot.getYAxis().setLineColor(axisColor); + plot.getYAxis().getAxisLabel().setFontColor(axisColor); + } }; //end of class diff --git a/OpenBCI_GUI/W_AnalogRead.pde b/OpenBCI_GUI/W_AnalogRead.pde index 9ec3f4123..9c5d8b45e 100644 --- a/OpenBCI_GUI/W_AnalogRead.pde +++ b/OpenBCI_GUI/W_AnalogRead.pde @@ -128,6 +128,14 @@ class W_AnalogRead extends WidgetWithSettings { analogModeButton.setPosition((int)(x0 + 1), (int)(y0 + NAV_HEIGHT + 1)); } + @Override + public void updateColors() { + super.updateColors(); // Update dropdown colors + for (AnalogReadBar bar : analogReadBars) { + bar.updatePlotColors(); + } + } + public void mousePressed() { super.mousePressed(); } @@ -270,9 +278,8 @@ class AnalogReadBar{ plot.setPointSize(2); plot.setPointColor(0); plot.setAllFontProperties("Arial", 0, 14); - plot.getXAxis().setFontColor(OPENBCI_DARKBLUE); - plot.getXAxis().setLineColor(OPENBCI_DARKBLUE); - plot.getXAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); + // Apply theme colors + updatePlotColors(); if(auxValuesPosition == 2) { plot.getXAxis().setAxisLabelText("Time (s)"); } @@ -440,4 +447,16 @@ class AnalogReadBar{ digitalPin.x = analogPin.x; digitalPin.y = analogPin.y + 12; } + + void updatePlotColors() { + // Apply theme colors to the analog read plot + color axisColor = style.getGraphAxisColor(); + plot.setBgColor(style.getGraphBackground()); + plot.setBoxBgColor(style.getGraphBoxBackground()); + plot.setBoxLineColor(style.getGraphLineColor()); + plot.setGridLineColor(style.getGraphGridColor()); + plot.getXAxis().setFontColor(axisColor); + plot.getXAxis().setLineColor(axisColor); + plot.getXAxis().getAxisLabel().setFontColor(axisColor); + } }; diff --git a/OpenBCI_GUI/W_BandPower.pde b/OpenBCI_GUI/W_BandPower.pde index 7043a04f6..9d2a70d53 100644 --- a/OpenBCI_GUI/W_BandPower.pde +++ b/OpenBCI_GUI/W_BandPower.pde @@ -95,12 +95,8 @@ class W_BandPower extends WidgetWithSettings { bp_plot.getXAxis().getAxisLabel().setOffset(42f); bp_plot.startHistograms(GPlot.VERTICAL); bp_plot.getHistogram().setDrawLabels(true); - bp_plot.getXAxis().setFontColor(OPENBCI_DARKBLUE); - bp_plot.getXAxis().setLineColor(OPENBCI_DARKBLUE); - bp_plot.getXAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); - bp_plot.getYAxis().setFontColor(OPENBCI_DARKBLUE); - bp_plot.getYAxis().setLineColor(OPENBCI_DARKBLUE); - bp_plot.getYAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); + // Apply theme colors + updatePlotColors(); //setting border of histograms to match BG bp_plot.getHistogram().setLineColors(new color[]{ @@ -162,7 +158,7 @@ class W_BandPower extends WidgetWithSettings { bp_plot.endDraw(); //for this widget need to redraw the grey bar, bc the FFT plot covers it up... - fill(200, 200, 200); + fill(style.isDefaultMode() ? color(200, 200, 200) : style.getBoxColor()); rect(x, y - NAV_HEIGHT, w, NAV_HEIGHT); //button bar popStyle(); @@ -249,6 +245,33 @@ class W_BandPower extends WidgetWithSettings { widgetSettings.set(FFTFilteredEnum.class, _filteredEnum); updateDropdownLabel(FFTFilteredEnum.class, "bandPowerDataFilteringDropdown"); } + + private void updatePlotColors() { + // Apply theme colors to the BandPower plot + color axisColor = style.getGraphAxisColor(); + color boxBgColor = style.getGraphBoxBackground(); + bp_plot.setBgColor(style.getGraphBackground()); + bp_plot.setBoxBgColor(boxBgColor); + bp_plot.setBoxLineColor(style.getGraphLineColor()); + bp_plot.setGridLineColor(style.getGraphGridColor()); + bp_plot.getXAxis().setFontColor(axisColor); + bp_plot.getXAxis().setLineColor(axisColor); + bp_plot.getXAxis().getAxisLabel().setFontColor(axisColor); + bp_plot.getYAxis().setFontColor(axisColor); + bp_plot.getYAxis().setLineColor(axisColor); + bp_plot.getYAxis().getAxisLabel().setFontColor(axisColor); + bp_plot.getHistogram().setFontColor(axisColor); + // Update histogram line colors to match background + bp_plot.getHistogram().setLineColors(new color[]{ + boxBgColor, boxBgColor, boxBgColor, boxBgColor, boxBgColor + }); + } + + @Override + public void updateColors() { + super.updateColors(); // Update dropdown colors + updatePlotColors(); + } }; public void bandPowerVerticalScaleDropdown(int n) { diff --git a/OpenBCI_GUI/W_DigitalRead.pde b/OpenBCI_GUI/W_DigitalRead.pde index 4e6272427..fc611030c 100644 --- a/OpenBCI_GUI/W_DigitalRead.pde +++ b/OpenBCI_GUI/W_DigitalRead.pde @@ -103,6 +103,14 @@ class W_DigitalRead extends Widget { } } } + + @Override + public void updateColors() { + super.updateColors(); + for (int i = 0; i < numDigitalReadDots; i++) { + digitalReadDots[i].updateColors(); + } + } public void screenResized() { super.screenResized(); @@ -197,6 +205,11 @@ class DigitalReadDot{ color dot1Fill = #f5f5f5; color val0Fill = OPENBCI_DARKBLUE; color val1Fill = WHITE; + + color getDotStroke() { return style.isDarkMode() ? style.getGraphLineColor() : dotStroke; } + color getDot0Fill() { return style.isDarkMode() ? style.getGraphBoxBackground() : dot0Fill; } + color getVal0Fill() { return style.isDarkMode() ? style.getSecondaryTextColor() : val0Fill; } + color getPinTextColor() { return style.isDarkMode() ? style.getSecondaryTextColor() : OPENBCI_DARKBLUE; } int dotX; int dotY; @@ -239,9 +252,13 @@ class DigitalReadDot{ drawDigitalValue = true; digitalPin = new TextBox("D" + digitalInputString, dotX, dotY - dotWidth); - digitalPin.textColor = OPENBCI_DARKBLUE; + digitalPin.textColor = getPinTextColor(); digitalPin.alignH = CENTER; } + + void updateColors() { + digitalPin.textColor = getPinTextColor(); + } void update() { List lastData = digitalBoard.getDataWithDigital(1); @@ -272,10 +289,10 @@ class DigitalReadDot{ fill(dot1Fill); digitalValue.textColor = val1Fill; } else { - fill(dot0Fill); - digitalValue.textColor = val0Fill; + fill(getDot0Fill()); + digitalValue.textColor = getVal0Fill(); } - stroke(dotStroke); + stroke(getDotStroke()); ellipse(dotX, dotY, dotWidth, dotHeight); if (drawDigitalValue) { diff --git a/OpenBCI_GUI/W_EMG.pde b/OpenBCI_GUI/W_EMG.pde index f53a6bc82..67d61d32a 100644 --- a/OpenBCI_GUI/W_EMG.pde +++ b/OpenBCI_GUI/W_EMG.pde @@ -140,11 +140,11 @@ class W_Emg extends WidgetWithSettings { //circle for outer threshold noFill(); strokeWeight(1); - stroke(OPENBCI_DARKBLUE, 150); + stroke(style.isDarkMode() ? style.getSecondaryTextColor() : OPENBCI_DARKBLUE, 150); circle(2*colOffset/8, rowOffset / 2, scaleFactor * emgSettingsValues.getUpperThreshold(channel)); //circle for inner threshold - stroke(OPENBCI_DARKBLUE, 150); + stroke(style.isDarkMode() ? style.getSecondaryTextColor() : OPENBCI_DARKBLUE, 150); circle(2*colOffset/8, rowOffset / 2, scaleFactor * emgSettingsValues.getLowerThreshold(channel)); int _x = int(5*colOffset/8); @@ -159,14 +159,14 @@ class W_Emg extends WidgetWithSettings { //draw background bar container for mapped uV value indication strokeWeight(1); - stroke(OPENBCI_DARKBLUE, 150); + stroke(style.isDarkMode() ? style.getSecondaryTextColor() : OPENBCI_DARKBLUE, 150); noFill(); rect(_x, _y, _w, _h); //draw channel number at upper left corner of row/column cell pushStyle(); - stroke(OPENBCI_DARKBLUE); - fill(OPENBCI_DARKBLUE); + stroke(style.isDarkMode() ? style.getTextColor() : OPENBCI_DARKBLUE); + fill(style.isDarkMode() ? style.getTextColor() : OPENBCI_DARKBLUE); textFont(h4, 14); text((channel + 1), 10, 20); popStyle(); diff --git a/OpenBCI_GUI/W_EMGJoystick.pde b/OpenBCI_GUI/W_EMGJoystick.pde index 203d1e8ba..feb29383a 100644 --- a/OpenBCI_GUI/W_EMGJoystick.pde +++ b/OpenBCI_GUI/W_EMGJoystick.pde @@ -181,12 +181,12 @@ class W_EmgJoystick extends WidgetWithSettings { */ //Background for graph - fill(graphBG); - stroke(graphStroke); + fill(style.isDarkMode() ? style.getGraphBoxBackground() : graphBG); + stroke(style.isDarkMode() ? style.getGraphLineColor() : graphStroke); circle(polarWindowX, polarWindowY, polarWindowDiameter); //X and Y axis lines - stroke(180); + stroke(style.isDarkMode() ? style.getGraphGridColor() : 180); line(polarWindowX - polarWindowHalfDiameter, polarWindowY, polarWindowX + polarWindowHalfDiameter, polarWindowY); line(polarWindowX, polarWindowY - polarWindowHalfDiameter, polarWindowX, polarWindowY + polarWindowHalfDiameter); @@ -309,7 +309,7 @@ class W_EmgJoystick extends WidgetWithSettings { private void drawChannelLabels() { pushStyle(); - fill(OPENBCI_DARKBLUE); + fill(style.isDarkMode() ? style.getTextColor() : OPENBCI_DARKBLUE); textFont(p4, 14); textLeading(14); textAlign(CENTER,CENTER); diff --git a/OpenBCI_GUI/W_FFT.pde b/OpenBCI_GUI/W_FFT.pde index fdf7ba292..2895e4778 100644 --- a/OpenBCI_GUI/W_FFT.pde +++ b/OpenBCI_GUI/W_FFT.pde @@ -100,12 +100,8 @@ class W_Fft extends WidgetWithSettings { fftPlot.getYAxis().setDrawTickLabels(true); fftPlot.setPointSize(2); fftPlot.setPointColor(0); - fftPlot.getXAxis().setFontColor(OPENBCI_DARKBLUE); - fftPlot.getXAxis().setLineColor(OPENBCI_DARKBLUE); - fftPlot.getXAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); - fftPlot.getYAxis().setFontColor(OPENBCI_DARKBLUE); - fftPlot.getYAxis().setLineColor(OPENBCI_DARKBLUE); - fftPlot.getYAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); + // Apply theme colors + updatePlotColors(); //setup points of fft point arrays for (int i = 0; i < fftGplotPoints.length; i++) { @@ -177,7 +173,7 @@ class W_Fft extends WidgetWithSettings { fftPlot.endDraw(); //for this widget need to redraw the grey bar, bc the FFT plot covers it up... - fill(200, 200, 200); + fill(style.isDefaultMode() ? color(200, 200, 200) : style.getBoxColor()); rect(x, y - NAV_HEIGHT, w, NAV_HEIGHT); //button bar popStyle(); @@ -255,6 +251,27 @@ class W_Fft extends WidgetWithSettings { widgetSettings.set(FFTFilteredEnum.class, _filteredEnum); updateDropdownLabel(FFTFilteredEnum.class, "fftFilteringDropdown"); } + + private void updatePlotColors() { + // Apply theme colors to the FFT plot + color axisColor = style.getGraphAxisColor(); + fftPlot.setBgColor(style.getGraphBackground()); + fftPlot.setBoxBgColor(style.getGraphBoxBackground()); + fftPlot.setBoxLineColor(style.getGraphLineColor()); + fftPlot.setGridLineColor(style.getGraphGridColor()); + fftPlot.getXAxis().setFontColor(axisColor); + fftPlot.getXAxis().setLineColor(axisColor); + fftPlot.getXAxis().getAxisLabel().setFontColor(axisColor); + fftPlot.getYAxis().setFontColor(axisColor); + fftPlot.getYAxis().setLineColor(axisColor); + fftPlot.getYAxis().getAxisLabel().setFontColor(axisColor); + } + + @Override + public void updateColors() { + super.updateColors(); // Update dropdown colors + updatePlotColors(); + } }; //These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected diff --git a/OpenBCI_GUI/W_Focus.pde b/OpenBCI_GUI/W_Focus.pde index fae7599eb..6cf6b91a3 100644 --- a/OpenBCI_GUI/W_Focus.pde +++ b/OpenBCI_GUI/W_Focus.pde @@ -213,6 +213,13 @@ class W_Focus extends WidgetWithSettings { super.mousePressed(); focusChanSelect.mousePressed(this.dropdownIsActive); //Calls channel select mousePressed and checks if clicked } + + @Override + public void updateColors() { + super.updateColors(); + dataGrid.updateDefaultTextColor(); + auditoryNeurofeedback.updateColors(); + } private void resizeTable() { int extraPadding = focusChanSelect.isVisible() ? NAV_HEIGHT : 0; @@ -329,6 +336,8 @@ class W_Focus extends WidgetWithSettings { ellipse(xc, yc, wc, hc); noStroke(); textAlign(CENTER); + // Use theme-aware text color for status label + fill(predictionExceedsThreshold ? cFocus : (style.isDarkMode() ? style.getTextColor() : cDark)); text(sb.toString(), xc, yc + hc/2 + 16); popStyle(); } diff --git a/OpenBCI_GUI/W_Marker.pde b/OpenBCI_GUI/W_Marker.pde index 9aa472ed9..aa455c760 100644 --- a/OpenBCI_GUI/W_Marker.pde +++ b/OpenBCI_GUI/W_Marker.pde @@ -148,6 +148,28 @@ class W_Marker extends WidgetWithSettings { localCP5.draw(); } + @Override + public void updateColors() { + super.updateColors(); // Update dropdown colors + markerBar.updatePlotColors(); + // Update button colors + color btnBg = style.isDarkMode() ? style.getButtonColor() : colorNotPressed; + color btnText = style.isDarkMode() ? style.getButtonTextColor() : OPENBCI_DARKBLUE; + for (Button btn : markerButtons) { + btn.setColorBackground(btnBg); + btn.getCaptionLabel().setColor(btnText); + } + // Update textfield colors + color tfBg = style.isDarkMode() ? style.getBoxColor() : color(255); + color tfText = style.isDarkMode() ? style.getTextColor() : OPENBCI_DARKBLUE; + markerReceiveIPTextfield.setColorBackground(tfBg); + markerReceiveIPTextfield.setColorValueLabel(tfText); + markerReceiveIPTextfield.setColor(tfText); + markerReceivePortTextfield.setColorBackground(tfBg); + markerReceivePortTextfield.setColorValueLabel(tfText); + markerReceivePortTextfield.setColor(tfText); + } + public void screenResized(){ super.screenResized(); @@ -475,12 +497,8 @@ class MarkerBar { plot.setAllFontProperties("Arial", 0, 14); plot.getXAxis().getAxisLabel().setOffset(float(X_AXIS_PADDING)); plot.getYAxis().getAxisLabel().setOffset(float(Y_AXIS_PADDING)); - plot.getXAxis().setFontColor(OPENBCI_DARKBLUE); - plot.getXAxis().setLineColor(OPENBCI_DARKBLUE); - plot.getXAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); - plot.getYAxis().setFontColor(OPENBCI_DARKBLUE); - plot.getYAxis().setLineColor(OPENBCI_DARKBLUE); - plot.getYAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); + // Apply theme colors + updatePlotColors(); gplotAutoscaler = new GPlotAutoscaler(false, AUTOSCALE_SPACING); initArrays(); @@ -573,5 +591,20 @@ class MarkerBar { plot.setDim(w - 36 - 4, h); } + + public void updatePlotColors() { + // Apply theme colors to the marker plot + color axisColor = style.getGraphAxisColor(); + plot.setBgColor(style.getGraphBackground()); + plot.setBoxBgColor(style.getGraphBoxBackground()); + plot.setBoxLineColor(style.getGraphLineColor()); + plot.setGridLineColor(style.getGraphGridColor()); + plot.getXAxis().setFontColor(axisColor); + plot.getXAxis().setLineColor(axisColor); + plot.getXAxis().getAxisLabel().setFontColor(axisColor); + plot.getYAxis().setFontColor(axisColor); + plot.getYAxis().setLineColor(axisColor); + plot.getYAxis().getAxisLabel().setFontColor(axisColor); + } }; diff --git a/OpenBCI_GUI/W_PacketLoss.pde b/OpenBCI_GUI/W_PacketLoss.pde index f8d8be27d..98b9791c3 100644 --- a/OpenBCI_GUI/W_PacketLoss.pde +++ b/OpenBCI_GUI/W_PacketLoss.pde @@ -133,7 +133,7 @@ class W_PacketLoss extends Widget { super.draw(); pushStyle(); - fill(OPENBCI_DARKBLUE); + fill(style.isDarkMode() ? style.getTextColor() : OPENBCI_DARKBLUE); textFont(p5, 12); text("Session length: " + sessionTimeElapsed.toString(), x + PADDING_5, y + 15); text("Stream length: " + streamTimeElapsed.toString(), x + PADDING_5, y + 35); @@ -141,6 +141,12 @@ class W_PacketLoss extends Widget { dataGrid.draw(); } + + @Override + public void updateColors() { + super.updateColors(); + dataGrid.updateDefaultTextColor(); + } public void screenResized(){ super.screenResized(); @@ -168,5 +174,4 @@ class W_PacketLoss extends Widget { private void resizeGrid() { dataGrid.setDim(x, y + TOP_PADDING, w); } - }; diff --git a/OpenBCI_GUI/W_PulseSensor.pde b/OpenBCI_GUI/W_PulseSensor.pde index f8344b07c..48a218c7b 100644 --- a/OpenBCI_GUI/W_PulseSensor.pde +++ b/OpenBCI_GUI/W_PulseSensor.pde @@ -108,12 +108,12 @@ class W_PulseSensor extends Widget { //remember to refer to x,y,w,h which are the positioning variables of the Widget class pushStyle(); - fill(graphBG); - stroke(graphStroke); + fill(style.isDarkMode() ? style.getGraphBoxBackground() : graphBG); + stroke(style.isDarkMode() ? style.getGraphLineColor() : graphStroke); rect(pulseWindowX,pulseWindowY,pulseWindowWidth,pulseWindowHeight); rect(bpmWindowX,bpmWindowY,bpmWindowWidth,bpmWindowHeight); - fill(50); + fill(style.isDarkMode() ? style.getSecondaryTextColor() : 50); textFont(p4, 16); textAlign(LEFT,CENTER); text("BPM "+bpmValue, bpmPositionX, bpmPositionY); diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde index 83ab17302..2e2255ac3 100644 --- a/OpenBCI_GUI/W_Spectrogram.pde +++ b/OpenBCI_GUI/W_Spectrogram.pde @@ -164,7 +164,7 @@ class W_Spectrogram extends WidgetWithSettings { private void drawBackground() { pushStyle(); - fill(0); + fill(style.isDarkMode() ? style.getGraphBoxBackground() : 0); rect(x, y, w, h); popStyle(); } diff --git a/OpenBCI_GUI/W_TimeSeries.pde b/OpenBCI_GUI/W_TimeSeries.pde index 26a1d4043..12bd19915 100644 --- a/OpenBCI_GUI/W_TimeSeries.pde +++ b/OpenBCI_GUI/W_TimeSeries.pde @@ -293,6 +293,15 @@ class W_TimeSeries extends WidgetWithSettings { } + @Override + public void updateColors() { + super.updateColors(); // Update dropdown colors + // Update all channel bar plot colors when theme changes + for (ChannelBar cb : channelBars) { + cb.updatePlotColors(); + } + } + void mousePressed() { super.mousePressed(); tsChanSelect.mousePressed(this.dropdownIsActive); //Calls channel select mousePressed and checks if clicked diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index 4a0abafc3..95f071b6b 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -62,18 +62,24 @@ class Widget { public void draw(){ pushStyle(); noStroke(); - fill(255); - rect(x,y-1,w,h+1); //draw white widget background + fill(style.getWidgetBackground()); + rect(x,y-1,w,h+1); //draw widget background popStyle(); //draw nav bars and button bars pushStyle(); - fill(150, 150, 150); + fill(style.isDefaultMode() ? color(150, 150, 150) : style.getSubNavBackground()); rect(x0, y0, w0, navH); //top bar - fill(200, 200, 200); + fill(style.isDefaultMode() ? color(200, 200, 200) : style.getBoxColor()); rect(x0, y0+navH, w0, navH); //button bar popStyle(); } + + // Called when theme changes - override in subclasses to update plot colors + public void updateColors() { + // Update dropdown colors for this widget + cp5_widget.setColor(dropdownColorsGlobal); + } public void addDropdown(String _id, String _title, List _items, int _defaultItem){ NavBarDropdown dropdownToAdd = new NavBarDropdown(_id, _title, _items, _defaultItem); @@ -189,7 +195,7 @@ class Widget { textFont(h5); textSize(12); textAlign(CENTER, BOTTOM); - fill(OPENBCI_DARKBLUE); + fill(style.isDarkMode() ? style.getTextColor() : OPENBCI_DARKBLUE); for(int i = 0; i < dropdowns.size(); i++){ int dropdownPos = dropdowns.size() - i; int _width = cp5_widget.getController(dropdowns.get(i).id).getWidth(); diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index 9363c9b81..b1f6fee12 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -349,6 +349,14 @@ class WidgetManager { return allWidgetSettings.toString(); } + public void updateAllWidgetColors() { + // Called when theme changes to update all widget colors + for (Widget widget : widgets) { + widget.updateColors(); + } + println("WidgetManager: Updated colors for all widgets"); + } + public void loadWidgetSettingsFromJson(String widgetSettingsJson) { JSONObject json = parseJSONObject(widgetSettingsJson); if (json == null) { diff --git a/release/wix/Package.wxs b/release/wix/Package.wxs index f837f85b6..62a0e6c5f 100644 --- a/release/wix/Package.wxs +++ b/release/wix/Package.wxs @@ -6,7 +6,7 @@ Name='OpenBCI GUI' Manufacturer='OpenBCI, Inc.' Version='7.0.0' - UpgradeCode='75eade24-72b2-4019-9256-c8a3411e5874' + UpgradeCode='45d666b4-ab22-4d2c-9ba3-1d143812dbc0' Compressed='yes' >