mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-23 01:21:40 +00:00
Add custom theming (#2081)
Adds support for user-created custom themes. Custom theme interface is tucked into the global settings in a non-invasive manner to avoid major design changes. Builds on the theme structure established by the dark theme update. <img width="1486" height="953" alt="image" src="https://github.com/user-attachments/assets/716bcfc7-af74-41dc-b14a-cfc2f2d2caa9" /> <img width="1486" height="956" alt="image" src="https://github.com/user-attachments/assets/a00f9620-0b1d-4f67-b010-e94dda5dc212" /> Here's a few examples of what teams could do, using a few color schemes from local teams. Imagine the possibilities! <img width="1485" height="951" alt="image" src="https://github.com/user-attachments/assets/c3da37b8-f6be-4152-81e0-533297f517fc" /> <img width="1483" height="951" alt="image" src="https://github.com/user-attachments/assets/0d453f7a-cf6f-4c27-97db-603b54c1f73e" /> <img width="1485" height="952" alt="image" src="https://github.com/user-attachments/assets/bf8c7770-e60d-4875-9580-ed7e54e089f4" /> <img width="1484" height="952" alt="image" src="https://github.com/user-attachments/assets/326d89e6-dd6e-4e05-a9fa-c9fc6f880847" /> <img width="1482" height="951" alt="image" src="https://github.com/user-attachments/assets/eb5a2a5d-c103-482c-a62a-5ccd5ba21cc5" /> <img width="1482" height="950" alt="image" src="https://github.com/user-attachments/assets/4831ca56-f322-4345-97af-8963ae8539b1" /> Looking for high contrast? Just moments away: <img width="1484" height="949" alt="image" src="https://github.com/user-attachments/assets/7ffc65c6-7000-4566-b4f0-c8247f75fb3d" />
This commit is contained in:
@@ -8,6 +8,7 @@ import PvSelect from "@/components/common/pv-select.vue";
|
||||
import { type ConfigurableNetworkSettings, NetworkConnectionType } from "@/types/SettingTypes";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
import { useTheme } from "vuetify";
|
||||
import { getThemeColor, setThemeColor, resetTheme } from "@/lib/ThemeManager";
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -19,6 +20,19 @@ const resetTempSettingsStruct = () => {
|
||||
|
||||
const settingsValid = ref(true);
|
||||
|
||||
const showThemeConfig = ref(false);
|
||||
const backgroundColor = ref("");
|
||||
const primaryColor = ref("");
|
||||
const secondaryColor = ref("");
|
||||
const surfaceColor = ref("");
|
||||
|
||||
const loadCurrentColors = () => {
|
||||
backgroundColor.value = getThemeColor(theme, "background");
|
||||
primaryColor.value = getThemeColor(theme, "primary");
|
||||
secondaryColor.value = getThemeColor(theme, "secondary");
|
||||
surfaceColor.value = getThemeColor(theme, "surface");
|
||||
};
|
||||
|
||||
const isValidNetworkTablesIP = (v: string | undefined): boolean => {
|
||||
// Check if it is a valid team number between 1-99999 (5 digits)
|
||||
const teamNumberRegex = /^[1-9][0-9]{0,4}$/;
|
||||
@@ -139,10 +153,23 @@ watchEffect(() => {
|
||||
|
||||
<template>
|
||||
<v-card class="mb-3 rounded-12" color="surface">
|
||||
<v-card-title>Global Settings</v-card-title>
|
||||
<v-card-title style="display: flex; justify-content: space-between">
|
||||
<span>Global Settings</span>
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="
|
||||
() => {
|
||||
loadCurrentColors();
|
||||
showThemeConfig = true;
|
||||
}
|
||||
"
|
||||
>
|
||||
<v-icon size="x-large">mdi-palette-outline</v-icon>
|
||||
Theme
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<div class="pa-5 pt-0">
|
||||
<v-divider class="pb-2" />
|
||||
<v-card-title class="pl-0 pt-3 pb-10px">Networking</v-card-title>
|
||||
<v-card-title class="pl-0 pt-0 pb-10px">Networking</v-card-title>
|
||||
<v-form ref="form" v-model="settingsValid">
|
||||
<pv-input
|
||||
v-model="tempSettingsStruct.ntServerAddress"
|
||||
@@ -203,7 +230,6 @@ watchEffect(() => {
|
||||
useSettingsStore().network.networkingDisabled
|
||||
"
|
||||
/>
|
||||
<v-divider class="mt-10px pb-2" />
|
||||
<v-card-title class="pl-0 pt-3 pb-10px">Advanced Networking</v-card-title>
|
||||
<pv-switch
|
||||
v-show="!useSettingsStore().network.networkingDisabled"
|
||||
@@ -254,7 +280,6 @@ watchEffect(() => {
|
||||
icon="mdi-information-outline"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
|
||||
/>
|
||||
<v-divider class="mt-10px pb-2" />
|
||||
<v-card-title class="pl-0 pt-3 pb-10px">Miscellaneous</v-card-title>
|
||||
<pv-switch
|
||||
v-model="tempSettingsStruct.shouldPublishProto"
|
||||
@@ -270,10 +295,10 @@ watchEffect(() => {
|
||||
icon="mdi-information-outline"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
|
||||
/>
|
||||
<v-divider class="mt-10px pb-5" />
|
||||
</v-form>
|
||||
<v-btn
|
||||
color="primary"
|
||||
class="mt-3"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
style="color: black; width: 100%"
|
||||
:disabled="!settingsValid || !settingsHaveChanged()"
|
||||
@@ -282,6 +307,84 @@ watchEffect(() => {
|
||||
Save
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-dialog v-model="showThemeConfig" width="800" dark>
|
||||
<v-card color="surface" flat>
|
||||
<v-card-title class="text-center">Theme Configuration</v-card-title>
|
||||
<v-card-text class="pt-0 pb-10px">
|
||||
<v-row>
|
||||
<v-col class="text-center">
|
||||
Background
|
||||
<v-color-picker
|
||||
class="ma-auto pt-3"
|
||||
elevation="0"
|
||||
mode="hex"
|
||||
:modes="['hex']"
|
||||
v-model:model-value="backgroundColor"
|
||||
v-on:update:model-value="(hex) => setThemeColor(theme, 'background', hex)"
|
||||
></v-color-picker>
|
||||
</v-col>
|
||||
<v-col class="text-center">
|
||||
Surface
|
||||
<v-color-picker
|
||||
class="ma-auto pt-3"
|
||||
elevation="0"
|
||||
mode="hex"
|
||||
:modes="['hex']"
|
||||
v-model:model-value="surfaceColor"
|
||||
v-on:update:model-value="(hex) => setThemeColor(theme, 'surface', hex)"
|
||||
></v-color-picker>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="text-center">
|
||||
Primary
|
||||
<v-color-picker
|
||||
class="ma-auto pt-3"
|
||||
elevation="0"
|
||||
mode="hex"
|
||||
:modes="['hex']"
|
||||
v-model:model-value="primaryColor"
|
||||
v-on:update:model-value="(hex) => setThemeColor(theme, 'primary', hex)"
|
||||
></v-color-picker>
|
||||
</v-col>
|
||||
<v-col class="text-center">
|
||||
Secondary
|
||||
<v-color-picker
|
||||
class="ma-auto pt-3"
|
||||
elevation="0"
|
||||
mode="hex"
|
||||
:modes="['hex']"
|
||||
v-model:model-value="secondaryColor"
|
||||
v-on:update:model-value="(hex) => setThemeColor(theme, 'secondary', hex)"
|
||||
></v-color-picker>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-5 pt-0">
|
||||
<v-btn
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
color="buttonPassive"
|
||||
class="text-black"
|
||||
@click="showThemeConfig = false"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
color="buttonActive"
|
||||
class="text-black"
|
||||
@click="
|
||||
() => {
|
||||
resetTheme(theme);
|
||||
loadCurrentColors();
|
||||
}
|
||||
"
|
||||
>
|
||||
Reset Default
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user