diff --git a/.github/images/pomotroid_themes-preview--914x219.png b/.github/images/pomotroid_themes-preview--914x219.png new file mode 100644 index 0000000..a834aca Binary files /dev/null and b/.github/images/pomotroid_themes-preview--914x219.png differ diff --git a/README.md b/README.md index d091549..1229214 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ - [x] **Customize round numbers, focus and break times** - [x] **Auto-start round** (optional) - [x] **Desktop notifications** (optional) +- [x] **Built-in [themes](#-themes)** +- [x] **Custom [themes](#-themes)** - [x] **Color gradiant** depending on the remaining time - [x] **Tray icon** with color gradiant - [x] **Minimize to tray** (optional) @@ -40,6 +42,14 @@ Download the install file for your OS from the latest release on https://github.com/vjousse/pomodorolm/releases/ +# 🎨 Themes + +Pomodorolm provides many themes. It's also theme-able, allowing you to customize its appearance. + +![Screenshots of Pomotroid using various themes](./.github/images/pomotroid_themes-preview--914x219.png) + +Visit the [theme documentation](./docs/themes/themes.md) to view the full list of official themes and for instruction on creating your own. + # 💻 Dev You will need to [install rust](https://www.rust-lang.org/tools/install) first. diff --git a/docs/themes/images/andromeda_01.png b/docs/themes/images/andromeda_01.png new file mode 100644 index 0000000..df84ef8 Binary files /dev/null and b/docs/themes/images/andromeda_01.png differ diff --git a/docs/themes/images/andromeda_02.png b/docs/themes/images/andromeda_02.png new file mode 100644 index 0000000..88ea15a Binary files /dev/null and b/docs/themes/images/andromeda_02.png differ diff --git a/docs/themes/images/ayu_01.png b/docs/themes/images/ayu_01.png new file mode 100644 index 0000000..3e4c810 Binary files /dev/null and b/docs/themes/images/ayu_01.png differ diff --git a/docs/themes/images/ayu_02.png b/docs/themes/images/ayu_02.png new file mode 100644 index 0000000..2044c1f Binary files /dev/null and b/docs/themes/images/ayu_02.png differ diff --git a/docs/themes/images/city-lights_01.png b/docs/themes/images/city-lights_01.png new file mode 100644 index 0000000..f4b249e Binary files /dev/null and b/docs/themes/images/city-lights_01.png differ diff --git a/docs/themes/images/city-lights_02.png b/docs/themes/images/city-lights_02.png new file mode 100644 index 0000000..3fff28b Binary files /dev/null and b/docs/themes/images/city-lights_02.png differ diff --git a/docs/themes/images/dracula_01.png b/docs/themes/images/dracula_01.png new file mode 100644 index 0000000..fc58ec8 Binary files /dev/null and b/docs/themes/images/dracula_01.png differ diff --git a/docs/themes/images/dracula_02.png b/docs/themes/images/dracula_02.png new file mode 100644 index 0000000..2b5cf1a Binary files /dev/null and b/docs/themes/images/dracula_02.png differ diff --git a/docs/themes/images/dva_01.png b/docs/themes/images/dva_01.png new file mode 100644 index 0000000..18eddb2 Binary files /dev/null and b/docs/themes/images/dva_01.png differ diff --git a/docs/themes/images/dva_02.png b/docs/themes/images/dva_02.png new file mode 100644 index 0000000..7cebd1c Binary files /dev/null and b/docs/themes/images/dva_02.png differ diff --git a/docs/themes/images/github_01.png b/docs/themes/images/github_01.png new file mode 100644 index 0000000..d671525 Binary files /dev/null and b/docs/themes/images/github_01.png differ diff --git a/docs/themes/images/github_02.png b/docs/themes/images/github_02.png new file mode 100644 index 0000000..e971da4 Binary files /dev/null and b/docs/themes/images/github_02.png differ diff --git a/docs/themes/images/graphite_01.png b/docs/themes/images/graphite_01.png new file mode 100644 index 0000000..b915ee0 Binary files /dev/null and b/docs/themes/images/graphite_01.png differ diff --git a/docs/themes/images/graphite_02.png b/docs/themes/images/graphite_02.png new file mode 100644 index 0000000..ffd827d Binary files /dev/null and b/docs/themes/images/graphite_02.png differ diff --git a/docs/themes/images/gruvbox_01.png b/docs/themes/images/gruvbox_01.png new file mode 100644 index 0000000..c40da7f Binary files /dev/null and b/docs/themes/images/gruvbox_01.png differ diff --git a/docs/themes/images/gruvbox_02.png b/docs/themes/images/gruvbox_02.png new file mode 100644 index 0000000..bf0d30d Binary files /dev/null and b/docs/themes/images/gruvbox_02.png differ diff --git a/docs/themes/images/monokai_01.png b/docs/themes/images/monokai_01.png new file mode 100644 index 0000000..f7db5ab Binary files /dev/null and b/docs/themes/images/monokai_01.png differ diff --git a/docs/themes/images/monokai_02.png b/docs/themes/images/monokai_02.png new file mode 100644 index 0000000..aad5c73 Binary files /dev/null and b/docs/themes/images/monokai_02.png differ diff --git a/docs/themes/images/nord_01.png b/docs/themes/images/nord_01.png new file mode 100644 index 0000000..f7a05ea Binary files /dev/null and b/docs/themes/images/nord_01.png differ diff --git a/docs/themes/images/nord_02.png b/docs/themes/images/nord_02.png new file mode 100644 index 0000000..65722ac Binary files /dev/null and b/docs/themes/images/nord_02.png differ diff --git a/docs/themes/images/one-dark-pro_01.png b/docs/themes/images/one-dark-pro_01.png new file mode 100644 index 0000000..dfd0652 Binary files /dev/null and b/docs/themes/images/one-dark-pro_01.png differ diff --git a/docs/themes/images/one-dark-pro_02.png b/docs/themes/images/one-dark-pro_02.png new file mode 100644 index 0000000..4065263 Binary files /dev/null and b/docs/themes/images/one-dark-pro_02.png differ diff --git a/docs/themes/images/pomotroid_01.png b/docs/themes/images/pomotroid_01.png new file mode 100644 index 0000000..f2dda37 Binary files /dev/null and b/docs/themes/images/pomotroid_01.png differ diff --git a/docs/themes/images/pomotroid_02.png b/docs/themes/images/pomotroid_02.png new file mode 100644 index 0000000..2632f38 Binary files /dev/null and b/docs/themes/images/pomotroid_02.png differ diff --git a/docs/themes/images/popping-and-locking_01.png b/docs/themes/images/popping-and-locking_01.png new file mode 100644 index 0000000..90a8476 Binary files /dev/null and b/docs/themes/images/popping-and-locking_01.png differ diff --git a/docs/themes/images/popping-and-locking_02.png b/docs/themes/images/popping-and-locking_02.png new file mode 100644 index 0000000..b19ec9a Binary files /dev/null and b/docs/themes/images/popping-and-locking_02.png differ diff --git a/docs/themes/images/solarized-light_01.png b/docs/themes/images/solarized-light_01.png new file mode 100644 index 0000000..6ae433b Binary files /dev/null and b/docs/themes/images/solarized-light_01.png differ diff --git a/docs/themes/images/solarized-light_02.png b/docs/themes/images/solarized-light_02.png new file mode 100644 index 0000000..fe8b678 Binary files /dev/null and b/docs/themes/images/solarized-light_02.png differ diff --git a/docs/themes/images/spandex_01.png b/docs/themes/images/spandex_01.png new file mode 100644 index 0000000..a187739 Binary files /dev/null and b/docs/themes/images/spandex_01.png differ diff --git a/docs/themes/images/spandex_02.png b/docs/themes/images/spandex_02.png new file mode 100644 index 0000000..3bd2635 Binary files /dev/null and b/docs/themes/images/spandex_02.png differ diff --git a/docs/themes/images/synthwave_01.png b/docs/themes/images/synthwave_01.png new file mode 100644 index 0000000..030ab2f Binary files /dev/null and b/docs/themes/images/synthwave_01.png differ diff --git a/docs/themes/images/synthwave_02.png b/docs/themes/images/synthwave_02.png new file mode 100644 index 0000000..4a1f1e6 Binary files /dev/null and b/docs/themes/images/synthwave_02.png differ diff --git a/docs/themes/images/tokyo-night-storm_01.png b/docs/themes/images/tokyo-night-storm_01.png new file mode 100644 index 0000000..b43ecf2 Binary files /dev/null and b/docs/themes/images/tokyo-night-storm_01.png differ diff --git a/docs/themes/images/tokyo-night-storm_02.png b/docs/themes/images/tokyo-night-storm_02.png new file mode 100644 index 0000000..4e73868 Binary files /dev/null and b/docs/themes/images/tokyo-night-storm_02.png differ diff --git a/docs/themes/theme-template.json b/docs/themes/theme-template.json new file mode 100644 index 0000000..c0dbc4a --- /dev/null +++ b/docs/themes/theme-template.json @@ -0,0 +1,17 @@ +{ + "name": "Theme Name", + "colors": { + "--color-long-round": "", + "--color-short-round": "", + "--color-focus-round": "", + "--color-focus-round-middle": "", + "--color-focus-round-end": "", + "--color-background": "", + "--color-background-light": "", + "--color-background-lightest": "", + "--color-foreground": "", + "--color-foreground-darker": "", + "--color-foreground-darkest": "", + "--color-accent": "" + } +} diff --git a/docs/themes/themes.md b/docs/themes/themes.md new file mode 100644 index 0000000..248d646 --- /dev/null +++ b/docs/themes/themes.md @@ -0,0 +1,67 @@ +# Pomotroid Themes + +Pomotroid comes with many officially supported themes. You can also add any number of custom themes. + +- [Pomotroid Themes](#pomotroid-themes) + - [Available Themes](#available-themes) + - [Creating a Custom Theme](#creating-a-custom-theme) + +## Available Themes + +These themes are available by default. + +| Theme | Main App | Timer Colors | +| ------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| Andromeda | ![Andromeda theme preview](images/andromeda_01.png) | ![Andromeda theme preview](images/andromeda_02.png) | +| Ayu Mirage | ![Ayu Mirage theme preview](images/ayu_01.png) | ![Ayu Mirage theme preview](images/ayu_02.png) | +| City Lights | ![City Lights theme preview](images/city-lights_01.png) | ![City Lights theme preview](images/city-lights_02.png) | +| Dracula | ![Dracula theme preview](images/dracula_01.png) | ![Dracula theme preview](images/dracula_02.png) | +| D.Va | ![D.Va theme preview](images/dva_01.png) | ![D.Va theme preview](images/dva_02.png) | +| GitHub | ![GitHub theme preview](images/github_01.png) | ![GitHub theme preview](images/github_02.png) | +| Graphite | ![Graphite theme preview](images/graphite_01.png) | ![Graphite theme preview](images/graphite_02.png) | +| Gruvbox | ![Gruvbox theme preview](images/gruvbox_01.png) | ![Gruvbox theme preview](images/gruvbox_02.png) | +| Monokai | ![Monokai theme preview](images/monokai_01.png) | ![Monokai theme preview](images/monokai_02.png) | +| Nord | ![Nord theme preview](images/nord_01.png) | ![Nord theme preview](images/nord_02.png) | +| One Dark Pro | ![One Dark Pro theme preview](images/one-dark-pro_01.png) | ![One Dark Pro theme preview](images/one-dark-pro_02.png) | +| Pomotroid (default) | ![Pomotroid theme preview](images/pomotroid_01.png) | ![Pomotroid theme preview](images/pomotroid_02.png) | +| Popping and Locking | ![Popping and Locking theme preview](images/popping-and-locking_01.png) | ![Popping and Locking theme preview](images/popping-and-locking_02.png) | +| Solarized Light | ![Solarized Light theme preview](images/solarized-light_01.png) | ![Solarized Light theme preview](images/solarized-light_02.png) | +| Spandex | ![Spandex theme preview](images/spandex_01.png) | ![Spandex theme preview](images/spandex_02.png) | +| Sythwave | ![Sythwave theme preview](images/synthwave_01.png) | ![Sythwave theme preview](images/synthwave_02.png) | +| Tokyo Night Storm | ![Tokyo Night Storm theme preview](images/tokyo-night-storm_01.png) | ![Tokyo Night Storm theme preview](images/tokyo-night-storm_02.png) | + +## Creating a Custom Theme + +Creating custom themes is simple. Themes are defined by a `json` file containing a **theme name** and several color values. Use the [theme template file](./theme-template.json) as a starting point. + +```json +{ + "name": "Theme Name", + "colors": { + "--color-long-round": "", + "--color-short-round": "", + "--color-focus-round": "", + "--color-focus-round-middle": "", + "--color-focus-round-end": "", + "--color-background": "", + "--color-background-light": "", + "--color-background-lightest": "", + "--color-foreground": "", + "--color-foreground-darker": "", + "--color-foreground-darkest": "", + "--color-accent": "" + } +} +``` + +`--color-focus-round-middle` and `--color-focus-round-end` are optional. You can use theme to customize the color of the gradiant during the focus round. If none are provided, a gradiant will be automatically computed from the `--color-focus-round` color to the `--color-short-round` color. + +To add your custom theme, copy your theme definition to the `pomodorolm/themes` directory in the `appData` directory. The location of the `appData` depends on the operating system. + +- `%APPDATA%` on **Windows** +- `$XDG_CONFIG_HOME` or `~/.config` on **Linux** +- `~/Library/Application Support` on **macOS** + +For example, add the theme file to the following directory on Windows: `C:\Users\{User}\AppData\Roaming\pomodorolm\themes` + +Restart the application to see your new theme available as an option. diff --git a/elm.json b/elm.json index 99a1a5d..b23ffe3 100644 --- a/elm.json +++ b/elm.json @@ -6,12 +6,14 @@ "elm-version": "0.19.1", "dependencies": { "direct": { + "NoRedInk/elm-json-decode-pipeline": "1.0.1", "elm/browser": "1.0.2", "elm/core": "1.0.3", "elm/html": "1.0.0", "elm/json": "1.1.3", "elm/svg": "1.0.1", - "elm/time": "1.0.0" + "elm/time": "1.0.0", + "rtfeldman/elm-hex": "1.0.0" }, "indirect": { "elm/url": "1.0.0", diff --git a/index.html b/index.html index 744032d..ce6ae82 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,19 @@ - +
diff --git a/main.css b/main.css index ba02b1a..ae616e1 100644 --- a/main.css +++ b/main.css @@ -10,16 +10,6 @@ } :root { - --color-long-round: #0bbddb; - --color-short-round: #05ec8c; - --color-focus-round: #ff4e4d; - --color-background: #2f384b; - --color-background-light: #3d4457; - --color-background-lightest: #858c99; - --color-foreground: #f6f2eb; - --color-foreground-darker: #c0c9da; - --color-foreground-darkest: #dbe1ef; - --color-accent: #05ec8c; font-size: 5vw; } @@ -115,7 +105,7 @@ body { } .title { - color: var(--color-short-round); + color: var(--color-focus-round); font-size: 1rem; font-weight: 200; padding-top: 1vw; @@ -529,6 +519,18 @@ input[type="range"]::-webkit-slider-thumb { height: 100%; } +#theme .setting-wrapper { + align-items: center; + border-left: 3px solid; + border-radius: 0 4px 4px 0; + display: flex; + justify-content: space-between; + margin: 12px 0; + min-height: 48px; + padding: 0 12px; + cursor: pointer; +} + #drawer .container { max-height: calc(100% - 10vw); overflow-y: auto; diff --git a/src-elm/ColorHelper.elm b/src-elm/ColorHelper.elm new file mode 100644 index 0000000..368bdf9 --- /dev/null +++ b/src-elm/ColorHelper.elm @@ -0,0 +1,99 @@ +-- Initial code is courtesy of to https://package.elm-lang.org/packages/juliusl/elm-ui-hexcolor/latest/Element-HexColor + + +module ColorHelper exposing (RGB(..), fromCSSHexToRGB, fromRGBToCSSHex) + +import Bitwise +import Dict exposing (Dict) +import Hex +import List + + +type RGB + = RGB Int Int Int + + +toStringWithZeroPadding : Int -> String +toStringWithZeroPadding num = + let + stringValue = + Hex.toString num + in + if String.length stringValue < 2 then + "0" ++ stringValue + + else + stringValue + + +fromCSSHexToRGB : String -> RGB +fromCSSHexToRGB hexcode = + RGB (getRed hexcode) (getGreen hexcode) (getBlue hexcode) + + +fromRGBToCSSHex : RGB -> String +fromRGBToCSSHex (RGB r g b) = + "#" ++ toStringWithZeroPadding r ++ toStringWithZeroPadding g ++ toStringWithZeroPadding b + + +getRed : String -> Int +getRed hexcode = + fromList (List.take 2 (fromCSSString hexcode)) + + +getGreen : String -> Int +getGreen hexcode = + fromList (List.take 2 (List.drop 2 (fromCSSString hexcode))) + + +getBlue : String -> Int +getBlue hexcode = + fromList (List.take 2 (List.drop 4 (fromCSSString hexcode))) + + +fromCSSString : String -> List Char +fromCSSString hexcode = + List.drop 1 (String.toList hexcode) + + +fromList : List Char -> Int +fromList chars = + List.sum (List.indexedMap (\i v -> Bitwise.shiftLeftBy (i * 4) v) (List.reverse (List.map fromChar chars))) + + +fromChar : Char -> Int +fromChar ch = + case Dict.get ch hexmap of + Just v -> + v + + Nothing -> + 0 + + +hexmap : Dict Char Int +hexmap = + Dict.fromList + [ ( '0', 0 ) + , ( '1', 1 ) + , ( '2', 2 ) + , ( '3', 3 ) + , ( '4', 4 ) + , ( '5', 5 ) + , ( '6', 6 ) + , ( '7', 7 ) + , ( '8', 8 ) + , ( '9', 9 ) + , ( 'A', 10 ) + , ( 'B', 11 ) + , ( 'C', 12 ) + , ( 'D', 13 ) + , ( 'E', 14 ) + , ( 'F', 15 ) + , ( 'a', 10 ) + , ( 'b', 11 ) + , ( 'c', 12 ) + , ( 'd', 13 ) + , ( 'e', 14 ) + , ( 'f', 15 ) + ] diff --git a/src-elm/ListWithCurrent.elm b/src-elm/ListWithCurrent.elm new file mode 100644 index 0000000..8f395da --- /dev/null +++ b/src-elm/ListWithCurrent.elm @@ -0,0 +1,180 @@ +module ListWithCurrent exposing (ListWithCurrent(..), addAfter, addAtEnd, addAtStart, addBefore, fromList, getCurrent, moveBackward, moveForward, setCurrentByPredicate, toList, updateCurrent) + + +type ListWithCurrent a + = EmptyListWithCurrent + | ListWithCurrent (List a) a (List a) + + + +-- Adding Element After the Current Element + + +addAfter : a -> ListWithCurrent a -> ListWithCurrent a +addAfter element listWithCurrent = + case listWithCurrent of + EmptyListWithCurrent -> + ListWithCurrent [] element [] + + ListWithCurrent prev current next -> + ListWithCurrent prev current (element :: next) + + + +-- Adding Element Before the Current Element + + +addBefore : a -> ListWithCurrent a -> ListWithCurrent a +addBefore element listWithCurrent = + case listWithCurrent of + EmptyListWithCurrent -> + ListWithCurrent [] element [] + + ListWithCurrent prev current next -> + ListWithCurrent (element :: prev) current next + + +addAtStart : a -> ListWithCurrent a -> ListWithCurrent a +addAtStart element listWithCurrent = + case listWithCurrent of + EmptyListWithCurrent -> + ListWithCurrent [] element [] + + ListWithCurrent prev current next -> + ListWithCurrent [] element (List.reverse prev ++ (current :: next)) + + +addAtEnd : a -> ListWithCurrent a -> ListWithCurrent a +addAtEnd element listWithCurrent = + case listWithCurrent of + EmptyListWithCurrent -> + ListWithCurrent [] element [] + + ListWithCurrent prev current next -> + ListWithCurrent (List.reverse next ++ (current :: prev)) element [] + + +fromList : List a -> ListWithCurrent a +fromList list = + case list of + [] -> + EmptyListWithCurrent + + x :: xs -> + ListWithCurrent [] x xs + + +toList : ListWithCurrent a -> List a +toList listWithCurrent = + case listWithCurrent of + EmptyListWithCurrent -> + [] + + ListWithCurrent prev current next -> + List.reverse prev ++ (current :: next) + + +getCurrent : ListWithCurrent a -> Maybe a +getCurrent listWithCurrent = + case listWithCurrent of + EmptyListWithCurrent -> + Nothing + + ListWithCurrent _ current _ -> + Just current + + +setCurrentByPredicate : (a -> Bool) -> ListWithCurrent a -> ListWithCurrent a +setCurrentByPredicate predicate listWithCurrent = + case listWithCurrent of + EmptyListWithCurrent -> + EmptyListWithCurrent + + ListWithCurrent prev current next -> + let + -- Combine all elements into a single list + combinedList = + List.reverse prev ++ (current :: next) + + -- Find the index of the element that matches the predicate + matchingIndex = + List.indexedMap + (\i elem -> + if predicate elem then + Just i + + else + Nothing + ) + combinedList + |> List.filterMap identity + |> List.head + + -- Split the combined list at the found index + ( before, after ) = + case matchingIndex of + Just idx -> + ( List.take idx combinedList, List.drop idx combinedList ) + + Nothing -> + ( [], combinedList ) + + -- If no match found, use the original list + -- Update `prev`, `current`, and `next` based on the new position + ( newPrev, newCurrentAndNext ) = + case after of + x :: xs -> + ( List.reverse before, x :: xs ) + + _ -> + ( List.reverse before, after ) + + ( newCurrent, newNext ) = + case newCurrentAndNext of + x :: xs -> + ( x, xs ) + + [] -> + ( current, [] ) + in + ListWithCurrent newPrev newCurrent newNext + + +updateCurrent : (a -> a) -> ListWithCurrent a -> ListWithCurrent a +updateCurrent updateFn listWithCurrent = + case listWithCurrent of + EmptyListWithCurrent -> + EmptyListWithCurrent + + ListWithCurrent prev current next -> + ListWithCurrent prev (updateFn current) next + + +moveBackward : ListWithCurrent a -> ListWithCurrent a +moveBackward listWithCurrent = + case listWithCurrent of + EmptyListWithCurrent -> + EmptyListWithCurrent + + ListWithCurrent [] current next -> + -- If there is no previous element, we cannot move backward + ListWithCurrent [] current next + + ListWithCurrent (prevHead :: prevTail) current next -> + -- Move the current element to the next list, and the previous head becomes the new current + ListWithCurrent prevTail prevHead (current :: next) + + +moveForward : ListWithCurrent a -> ListWithCurrent a +moveForward listWithCurrent = + case listWithCurrent of + EmptyListWithCurrent -> + EmptyListWithCurrent + + ListWithCurrent prev current [] -> + -- If there is no next element, we cannot move forward + ListWithCurrent prev current [] + + ListWithCurrent prev current (nextHead :: nextTail) -> + -- Move the current element to the prev list, and the next head becomes the new current + ListWithCurrent (current :: prev) nextHead nextTail diff --git a/src-elm/Main.elm b/src-elm/Main.elm index 98c9453..fe0d74c 100644 --- a/src-elm/Main.elm +++ b/src-elm/Main.elm @@ -1,13 +1,17 @@ port module Main exposing (..) import Browser +import ColorHelper exposing (RGB(..), fromCSSHexToRGB, fromRGBToCSSHex) import Html exposing (Html, a, div, h1, h2, input, nav, p, section, text) import Html.Attributes exposing (attribute, class, href, id, style, target, title, type_, value) import Html.Events exposing (onClick, onInput, onMouseLeave) -import Json.Decode +import Json.Decode as Decode +import Json.Decode.Pipeline as Pipe import Json.Encode +import ListWithCurrent exposing (ListWithCurrent(..)) import Svg exposing (path, svg) import Svg.Attributes as SvgAttr +import Themes exposing (Theme, ThemeColors, pomodorolmTheme) main : Program Flags Model Msg @@ -27,28 +31,23 @@ type alias Seconds = type alias Model = { appVersion : String , config : Config - , currentColor : Color + , currentColor : RGB , currentRoundNumber : Int , currentSessionType : SessionType , currentState : CurrentState , currentTime : Seconds , drawerOpen : Bool - , endColor : Color - , initialColor : Color - , middleColor : Color , muted : Bool , sessionStatus : SessionStatus , settingTab : SettingTab , strokeDasharray : Float + , theme : Theme + , themes : ListWithCurrent Theme , volume : Float , volumeSliderHidden : Bool } -type alias Color = - { r : Int, g : Int, b : Int } - - type alias Config = { alwaysOnTop : Bool , autoStartBreakTimer : Bool @@ -60,31 +59,62 @@ type alias Config = , minimizeToTrayOnClose : Bool , pomodoroDuration : Seconds , shortBreakDuration : Seconds + , theme : String , tickSoundsDuringBreak : Bool , tickSoundsDuringWork : Bool } -configDecoder : Json.Decode.Decoder Config +themeColorsDecoder : Decode.Decoder ThemeColors +themeColorsDecoder = + Decode.succeed ThemeColors + |> Pipe.required "accent" Decode.string + |> Pipe.required "background" Decode.string + |> Pipe.required "background_light" Decode.string + |> Pipe.required "background_lightest" Decode.string + |> Pipe.required "focus_round" Decode.string + |> Pipe.required "focus_round_end" Decode.string + |> Pipe.required "focus_round_middle" Decode.string + |> Pipe.required "foreground" Decode.string + |> Pipe.required "foreground_darker" Decode.string + |> Pipe.required "foreground_darkest" Decode.string + |> Pipe.required "long_round" Decode.string + |> Pipe.required "short_round" Decode.string + + +themeDecoder : Decode.Decoder Theme +themeDecoder = + Decode.succeed Theme + |> Pipe.required "colors" themeColorsDecoder + |> Pipe.required "name" Decode.string + + +themesDecoder : Decode.Decoder (List Theme) +themesDecoder = + Decode.list themeDecoder + + +configDecoder : Decode.Decoder Config configDecoder = let fieldSet0 = - Json.Decode.map8 Config - (Json.Decode.field "always_on_top" Json.Decode.bool) - (Json.Decode.field "auto_start_break_timer" Json.Decode.bool) - (Json.Decode.field "auto_start_work_timer" Json.Decode.bool) - (Json.Decode.field "desktop_notifications" Json.Decode.bool) - (Json.Decode.field "long_break_duration" Json.Decode.int) - (Json.Decode.field "max_round_number" Json.Decode.int) - (Json.Decode.field "minimize_to_tray" Json.Decode.bool) - (Json.Decode.field "minimize_to_tray_on_close" Json.Decode.bool) + Decode.map8 Config + (Decode.field "always_on_top" Decode.bool) + (Decode.field "auto_start_break_timer" Decode.bool) + (Decode.field "auto_start_work_timer" Decode.bool) + (Decode.field "desktop_notifications" Decode.bool) + (Decode.field "long_break_duration" Decode.int) + (Decode.field "max_round_number" Decode.int) + (Decode.field "minimize_to_tray" Decode.bool) + (Decode.field "minimize_to_tray_on_close" Decode.bool) in - Json.Decode.map5 (<|) + Decode.map6 (<|) fieldSet0 - (Json.Decode.field "pomodoro_duration" Json.Decode.int) - (Json.Decode.field "short_break_duration" Json.Decode.int) - (Json.Decode.field "tick_sounds_during_break" Json.Decode.bool) - (Json.Decode.field "tick_sounds_during_work" Json.Decode.bool) + (Decode.field "pomodoro_duration" Decode.int) + (Decode.field "short_break_duration" Decode.int) + (Decode.field "theme" Decode.string) + (Decode.field "tick_sounds_during_break" Decode.bool) + (Decode.field "tick_sounds_during_work" Decode.bool) encodedConfig : Config -> Json.Encode.Value @@ -106,7 +136,7 @@ encodedConfig config = type alias CurrentState = - { color : Color, percentage : Float, paused : Bool, playTick : Bool } + { color : String, percentage : Float, paused : Bool, playTick : Bool } type SessionType @@ -123,6 +153,7 @@ type SessionStatus type SettingTab = TimerTab + | ThemeTab | SettingsTab | AboutTab @@ -177,6 +208,7 @@ type alias Flags = , minimizeToTrayOnClose : Bool , pomodoroDuration : Seconds , shortBreakDuration : Seconds + , theme : String , tickSoundsDuringWork : Bool , tickSoundsDuringBreak : Bool } @@ -191,36 +223,14 @@ defaults = } -green : Color -green = - { r = 5, g = 236, b = 140 } - - -orange : Color -orange = - { r = 255, g = 127, b = 14 } - - -red : Color -red = - { r = 255, g = 78, b = 77 } - - -blue : Color -blue = - { r = 11, g = 189, b = 219 } - - -pink : Color -pink = - { r = 255, g = 137, b = 167 } - - init : Flags -> ( Model, Cmd Msg ) init flags = let + theme = + pomodorolmTheme + currentState = - { color = green + { color = theme.colors.focusRound , percentage = 1 , paused = False , playTick = False @@ -238,45 +248,48 @@ init flags = , minimizeToTrayOnClose = flags.minimizeToTrayOnClose , pomodoroDuration = flags.pomodoroDuration , shortBreakDuration = flags.shortBreakDuration + , theme = flags.theme , tickSoundsDuringWork = flags.tickSoundsDuringWork , tickSoundsDuringBreak = flags.tickSoundsDuringBreak } - , currentColor = green + , currentColor = fromCSSHexToRGB theme.colors.focusRound , currentRoundNumber = 1 , currentSessionType = Pomodoro , currentState = currentState , currentTime = flags.pomodoroDuration , drawerOpen = False - , endColor = red - , initialColor = green - , middleColor = orange , muted = False , sessionStatus = Stopped , settingTab = TimerTab , strokeDasharray = 691.3321533203125 + , theme = theme + , themes = ListWithCurrent.fromList [ theme ] , volume = 1 , volumeSliderHidden = True } , Cmd.batch [ updateCurrentState currentState , loadRustConfig () + , setThemeColors <| theme.colors ] ) type SettingType = FocusTime - | ShortBreakTime | LongBreakTime | Rounds + | ShortBreakTime type Msg = CloseWindow | ChangeSettingTab SettingTab | ChangeSettingConfig Setting + | ChangeTheme Theme | HideVolumeBar | LoadConfig Config + | LoadThemes (List Theme) | MinimizeWindow | NoOp | Reset @@ -287,8 +300,8 @@ type Msg | ToggleDrawer | ToggleMute | ToggleStatus - | UpdateVolume String | UpdateSetting SettingType String + | UpdateVolume String getNextRoundInfo : Model -> NextRoundInfo @@ -297,8 +310,10 @@ getNextRoundInfo model = getNotification : String -> String -> String -> Seconds -> SessionType -> Notification getNotification title body name duration sessionType = let - color = - computeCurrentColor 1 1 sessionType + ( r, g, b ) = + case computeCurrentColor 1 1 sessionType model.theme of + RGB red_ green_ blue_ -> + ( red_, green_, blue_ ) minutes = (duration |> toFloat) / 60 |> round @@ -317,9 +332,9 @@ getNextRoundInfo model = ++ " " ++ body , name = name - , red = color.r - , green = color.g - , blue = color.b + , red = r + , green = g + , blue = b } in case model.currentSessionType of @@ -359,7 +374,7 @@ getNextRoundInfo model = update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = - case msg of + case Debug.log "MSG" msg of ChangeSettingConfig settingConfig -> let settingsConfig = @@ -405,6 +420,34 @@ update msg model = ChangeSettingTab settingTab -> ( { model | settingTab = settingTab }, Cmd.none ) + ChangeTheme theme -> + let + currentState = + model.currentState + + newState = + { currentState | color = fromRGBToCSSHex <| colorForSessionType model.currentSessionType theme } + + config = + model.config + + newConfig = + { config + | theme = theme.name |> String.toLower + } + in + ( { model + | config = newConfig + , currentState = newState + , theme = theme + } + , Cmd.batch + [ setThemeColors theme.colors + , updateConfig newConfig + , updateCurrentState newState + ] + ) + CloseWindow -> ( model , if model.config.minimizeToTrayOnClose then @@ -418,22 +461,82 @@ update msg model = ( { model | volumeSliderHidden = True }, Cmd.none ) LoadConfig config -> - ( { model - | config = config - , sessionStatus = Stopped - , currentTime = - case model.currentSessionType of - Pomodoro -> - config.pomodoroDuration + let + updatedThemes = + model.themes + |> ListWithCurrent.setCurrentByPredicate (\t -> (t.name |> String.toLower) == config.theme) - ShortBreak -> - config.shortBreakDuration + newThemes = + case ListWithCurrent.getCurrent updatedThemes of + Just theme -> + -- We found a theme with the same name than in the config: everything's fine + if (theme.name |> String.toLower) == (model.config.theme |> String.toLower) then + updatedThemes - LongBreak -> - config.longBreakDuration - } - , Cmd.none - ) + else + -- If we didn't found a corresponding theme name, pomodorolm should be the default theme + updatedThemes |> ListWithCurrent.setCurrentByPredicate (\t -> (t.name |> String.toLower) == "pomodorolm") + + Nothing -> + updatedThemes + + newModel = + { model + | config = config + , sessionStatus = Stopped + , themes = newThemes + , currentTime = + case model.currentSessionType of + Pomodoro -> + config.pomodoroDuration + + ShortBreak -> + config.shortBreakDuration + + LongBreak -> + config.longBreakDuration + } + in + case newThemes |> ListWithCurrent.getCurrent of + Just currentTheme -> + update (ChangeTheme currentTheme) newModel + + _ -> + ( newModel, Cmd.none ) + + LoadThemes themes -> + let + loadedThemes = + themes + |> List.sortBy .name + |> ListWithCurrent.fromList + |> ListWithCurrent.setCurrentByPredicate (\t -> (t.name |> String.toLower) == model.config.theme) + + newThemes = + case Debug.log "Current" (ListWithCurrent.getCurrent loadedThemes) of + Just theme -> + -- We found a theme with the same name than in the config: everything's fine + if (theme.name |> String.toLower) == (model.config.theme |> String.toLower) then + loadedThemes + + else + -- If we didn't found a corresponding theme name, pomodorolm should be the default theme + loadedThemes |> ListWithCurrent.setCurrentByPredicate (\t -> (t.name |> String.toLower) == "pomodorolm") + + Nothing -> + loadedThemes + + newModel = + { model + | themes = newThemes + } + in + case newThemes |> ListWithCurrent.getCurrent of + Just currentTheme -> + update (ChangeTheme currentTheme) newModel + + _ -> + ( newModel, Cmd.none ) MinimizeWindow -> ( model @@ -450,7 +553,7 @@ update msg model = Reset -> let currentState = - { color = colorForSessionType model.currentSessionType + { color = fromRGBToCSSHex <| colorForSessionType model.currentSessionType model.theme , percentage = 100 , paused = if model.sessionStatus == Paused then @@ -534,7 +637,7 @@ update msg model = } currentState = - { color = colorForSessionType nextRoundInfo.nextSessionType + { color = fromRGBToCSSHex <| colorForSessionType nextRoundInfo.nextSessionType model.theme , percentage = 100 , paused = if model.sessionStatus == Paused then @@ -574,7 +677,7 @@ update msg model = getCurrentMaxTime model currentColor = - computeCurrentColor newTime maxTime model.currentSessionType + computeCurrentColor newTime maxTime model.currentSessionType model.theme percent = 1 * toFloat newTime / toFloat maxTime @@ -586,7 +689,7 @@ update msg model = } currentState = - { color = currentColor + { color = fromRGBToCSSHex currentColor , percentage = percent , paused = if model.sessionStatus == Paused then @@ -634,7 +737,7 @@ update msg model = } currentState = - { color = colorForSessionType nextRoundInfo.nextSessionType + { color = fromRGBToCSSHex <| colorForSessionType nextRoundInfo.nextSessionType model.theme , percentage = 100 , paused = if nextModel.sessionStatus == Paused then @@ -757,7 +860,13 @@ update msg model = { model | sessionStatus = Paused } currentState = - { color = computeCurrentColor model.currentTime (getCurrentMaxTime model) model.currentSessionType + { color = + fromRGBToCSSHex <| + computeCurrentColor + model.currentTime + (getCurrentMaxTime model) + model.currentSessionType + model.theme , percentage = 1 * toFloat model.currentTime / toFloat (getCurrentMaxTime model) , paused = True , playTick = shouldPlayTick nextModel @@ -773,7 +882,12 @@ update msg model = { model | sessionStatus = Running } currentState = - { color = computeCurrentColor model.currentTime (getCurrentMaxTime model) model.currentSessionType + { color = + fromRGBToCSSHex <| + computeCurrentColor model.currentTime + (getCurrentMaxTime model) + model.currentSessionType + model.theme , percentage = 1 * toFloat model.currentTime / toFloat (getCurrentMaxTime model) , paused = False , playTick = shouldPlayTick nextModel @@ -825,7 +939,7 @@ update msg model = , updateConfig newConfig ) - ShortBreakTime -> + LongBreakTime -> let newValue = if value > 90 then @@ -835,9 +949,9 @@ update msg model = value * 60 in ( { model - | config = { config | shortBreakDuration = newValue } + | config = { config | longBreakDuration = newValue } , currentTime = - if model.currentSessionType == ShortBreak then + if model.currentSessionType == LongBreak then if newValue == 0 then 60 @@ -850,7 +964,22 @@ update msg model = , Cmd.none ) - LongBreakTime -> + Rounds -> + let + newValue = + if value > 12 then + 12 + + else + value + in + ( { model + | config = { config | maxRoundNumber = newValue } + } + , Cmd.none + ) + + ShortBreakTime -> let newValue = if value > 90 then @@ -860,9 +989,9 @@ update msg model = value * 60 in ( { model - | config = { config | longBreakDuration = newValue } + | config = { config | shortBreakDuration = newValue } , currentTime = - if model.currentSessionType == LongBreak then + if model.currentSessionType == ShortBreak then if newValue == 0 then 60 @@ -875,21 +1004,6 @@ update msg model = , Cmd.none ) - Rounds -> - let - newValue = - if value > 12 then - 12 - - else - value - in - ( { model - | config = { config | maxRoundNumber = newValue } - } - , Cmd.none - ) - UpdateVolume volumeStr -> let newVolume = @@ -937,21 +1051,21 @@ shouldPlayTick model = False -colorForSessionType : SessionType -> Color -colorForSessionType sessionType = +colorForSessionType : SessionType -> Theme -> RGB +colorForSessionType sessionType theme = case sessionType of Pomodoro -> - green + fromCSSHexToRGB <| theme.colors.focusRound ShortBreak -> - pink + fromCSSHexToRGB <| theme.colors.shortRound LongBreak -> - blue + fromCSSHexToRGB <| theme.colors.longRound -computeCurrentColor : Seconds -> Seconds -> SessionType -> Color -computeCurrentColor currentTime maxTime sessionType = +computeCurrentColor : Seconds -> Seconds -> SessionType -> Theme -> RGB +computeCurrentColor currentTime maxTime sessionType theme = let percent = 1 * toFloat currentTime / toFloat maxTime @@ -961,20 +1075,35 @@ computeCurrentColor currentTime maxTime sessionType = in case sessionType of Pomodoro -> + let + ( startRed, startGreen, startBlue ) = + case fromCSSHexToRGB theme.colors.focusRound of + RGB r g b -> + ( r, g, b ) + + ( middleRed, middleGreen, middleBlue ) = + case fromCSSHexToRGB theme.colors.focusRoundMiddle of + RGB r g b -> + ( r, g, b ) + + ( endRed, endGreen, endBlue ) = + case fromCSSHexToRGB theme.colors.focusRoundEnd of + RGB r g b -> + ( r, g, b ) + in if percent > 0.5 then - { r = toFloat orange.r + (relativePercent * toFloat (green.r - orange.r)) |> round - , g = toFloat orange.g + (relativePercent * toFloat (green.g - orange.g)) |> round - , b = toFloat orange.b + (relativePercent * toFloat (green.b - orange.b)) |> round - } + RGB + (toFloat middleRed + (relativePercent * toFloat (startRed - middleRed)) |> round) + (toFloat middleGreen + (relativePercent * toFloat (startGreen - middleGreen)) |> round) + (toFloat middleBlue + (relativePercent * toFloat (startBlue - middleBlue)) |> round) else - { r = toFloat red.r + ((1 + relativePercent) * toFloat (orange.r - red.r)) |> round - , g = toFloat red.g + ((1 + relativePercent) * toFloat (orange.g - red.g)) |> round - , b = toFloat red.b + ((1 + relativePercent) * toFloat (orange.b - red.b)) |> round - } + RGB (toFloat endRed + ((1 + relativePercent) * toFloat (middleRed - endRed)) |> round) + (toFloat endGreen + ((1 + relativePercent) * toFloat (middleGreen - endGreen)) |> round) + (toFloat endBlue + ((1 + relativePercent) * toFloat (middleBlue - endBlue)) |> round) s -> - colorForSessionType s + colorForSessionType s theme secondsToString : Seconds -> String @@ -982,8 +1111,8 @@ secondsToString seconds = (String.padLeft 2 '0' <| String.fromInt (seconds // 60)) ++ ":" ++ (String.padLeft 2 '0' <| String.fromInt (modBy 60 seconds)) -dialView : SessionType -> Seconds -> Seconds -> Float -> Html Msg -dialView sessionType currentTime maxTime maxStrokeDasharray = +dialView : SessionType -> Seconds -> Seconds -> Float -> Theme -> Html Msg +dialView sessionType currentTime maxTime maxStrokeDasharray theme = let percent = 1 * toFloat currentTime / toFloat maxTime @@ -991,11 +1120,11 @@ dialView sessionType currentTime maxTime maxStrokeDasharray = strokeDasharray = maxStrokeDasharray - maxStrokeDasharray * percent - colorToHtmlRgbString c = - "rgb(" ++ String.fromInt c.r ++ ", " ++ String.fromInt c.g ++ ", " ++ String.fromInt c.b ++ ")" + colorToHtmlRgbString (RGB r g b) = + "rgb(" ++ String.fromInt r ++ ", " ++ String.fromInt g ++ ", " ++ String.fromInt b ++ ")" color = - colorToHtmlRgbString <| computeCurrentColor currentTime maxTime sessionType + colorToHtmlRgbString <| computeCurrentColor currentTime maxTime sessionType theme in div [ class "dial-wrapper" ] [ p [ class "dial-time" ] @@ -1256,7 +1385,7 @@ getCurrentMaxTime model = timerView : Model -> Html Msg timerView model = div [ class "timer-wrapper" ] - [ dialView model.currentSessionType model.currentTime (getCurrentMaxTime model) model.strokeDasharray + [ dialView model.currentSessionType model.currentTime (getCurrentMaxTime model) model.strokeDasharray model.theme , playPauseView model.sessionStatus , footerView model ] @@ -1609,12 +1738,56 @@ aboutSettingView appVersion = ] +themeSettingView : Model -> Html Msg +themeSettingView model = + div [ class "container", id "theme" ] + (p [ class "drawer-heading" ] [ text "Themes" ] + :: (model.themes + |> ListWithCurrent.toList + |> List.map + (\t -> + let + name = + t.name + + colors = + t.colors + in + div + [ class "setting-wrapper" + , style "background-color" colors.background + , style "border-color" colors.accent + , onClick <| ChangeTheme t + ] + [ p + [ class "setting-title" + , style "color" colors.foreground + ] + [ text name ] + , if t == model.theme then + svg + [ SvgAttr.viewBox "0 0 24 24" + , SvgAttr.width "5vw" + ] + [ path [ SvgAttr.fill colors.accent, SvgAttr.d "M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" ] [] ] + + else + text "" + ] + ) + ) + ) + + drawerView : Model -> Html Msg drawerView model = div [ id "drawer" ] [ case model.settingTab of + ThemeTab -> + themeSettingView model + TimerTab -> timerSettingView model @@ -1705,6 +1878,40 @@ drawerView model = ] ] ] + , div + [ title "Options" + , class "drawer-menu-wrapper" + , class + (if model.settingTab == ThemeTab then + "is-active" + + else + "" + ) + , onClick <| ChangeSettingTab ThemeTab + ] + [ div + [ class "drawer-menu-button" + ] + [ svg + [ SvgAttr.version "1.2" + , SvgAttr.baseProfile "tiny" + , SvgAttr.id "theme-icon" + , SvgAttr.x "0px" + , SvgAttr.y "0px" + , SvgAttr.viewBox "0 0 19.5 20" + , SvgAttr.width "5vw" + , SvgAttr.xmlSpace "preserve" + , SvgAttr.class "icon" + ] + [ path + [ SvgAttr.fill "var(--color-background-lightest)" + , SvgAttr.d "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" + ] + [] + ] + ] + ] , div [ title "About" , class "drawer-menu-wrapper" @@ -1764,9 +1971,9 @@ view model = -- SUBSCRIPTIONS -mapLoadConfig : Json.Decode.Value -> Msg +mapLoadConfig : Decode.Value -> Msg mapLoadConfig modelJson = - case Json.Decode.decodeValue configDecoder modelJson of + case Decode.decodeValue configDecoder modelJson of Ok model -> LoadConfig model @@ -1775,18 +1982,33 @@ mapLoadConfig modelJson = NoOp +mapLoadThemes : Decode.Value -> Msg +mapLoadThemes modelJson = + case Decode.decodeValue themesDecoder modelJson of + Ok themes -> + LoadThemes themes + + Err _ -> + --@FIX: don't fail silently + NoOp + + subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ tick Tick , loadConfig mapLoadConfig + , loadThemes mapLoadThemes ] port tick : (String -> msg) -> Sub msg -port loadConfig : (Json.Decode.Value -> msg) -> Sub msg +port loadConfig : (Decode.Value -> msg) -> Sub msg + + +port loadThemes : (Decode.Value -> msg) -> Sub msg @@ -1821,3 +2043,6 @@ port notify : Notification -> Cmd msg port updateConfig : Config -> Cmd msg + + +port setThemeColors : ThemeColors -> Cmd msg diff --git a/src-elm/Themes.elm b/src-elm/Themes.elm new file mode 100644 index 0000000..35234f7 --- /dev/null +++ b/src-elm/Themes.elm @@ -0,0 +1,48 @@ +module Themes exposing (RGBColor(..), Theme, ThemeColors, pomodorolmTheme) + + +type RGBColor + = RGB Int Int Int + | RGBA Int Int Int Float + + +type alias Theme = + { colors : ThemeColors + , name : String + } + + +type alias ThemeColors = + { accent : String + , background : String + , backgroundLight : String + , backgroundLightest : String + , focusRound : String + , focusRoundEnd : String + , focusRoundMiddle : String + , foreground : String + , foregroundDarker : String + , foregroundDarkest : String + , longRound : String + , shortRound : String + } + + +pomodorolmTheme : Theme +pomodorolmTheme = + { colors = + { longRound = "#0bbddb" + , shortRound = "#ff4e4d" + , focusRound = "#05ec8c" + , focusRoundMiddle = "#ff7f0e" + , focusRoundEnd = "#ff4e4d" + , background = "#2f384b" + , backgroundLight = "#3d4457" + , backgroundLightest = "#858c99" + , foreground = "#f6f2eb" + , foregroundDarker = "#c0c9da" + , foregroundDarkest = "#dbe1ef" + , accent = "#05ec8c" + } + , name = "Pomodorolm" + } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 3dbcf24..9f93bca 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1887,6 +1887,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex_color" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37f101bf4c633f7ca2e4b5e136050314503dd198e78e325ea602c327c484ef0" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "hound" version = "3.5.1" @@ -3333,6 +3342,7 @@ name = "pomodorolm" version = "0.1.0" dependencies = [ "futures", + "hex_color", "image 0.25.2", "rodio", "serde", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a38f7c7..2760c5d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -2,7 +2,7 @@ name = "pomodorolm" version = "0.1.0" description = "A Tauri App" -authors = ["you"] +authors = ["Vincent Jousse"] license = "" repository = "" edition = "2021" @@ -27,6 +27,7 @@ tauri-plugin-shell = "2.0.0-beta" tauri-plugin-notification = "2.0.0-beta" tauri-plugin-log = "2.0.0-beta" tokio-stream = "0.1.15" +hex_color = "3.0.0" [features] # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. # If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes. diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c9026e7..2521493 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -18,19 +18,23 @@ use tokio::time; // 1.3.0 // pub struct AppState(Arc