Radish alpha
r
rad:z39mP9rQAaGmERfUMPULfPUi473tY
Radicle terminal user interface
Radicle
Git
bin: Detect light / dark theme and use in apps
Erik Kundt committed 3 months ago
commit 4e2f75dd7322f7759d17ad2cbfc06a879225062c
parent f3cae9f
7 files changed +79 -57
modified bin/commands/inbox/list.rs
@@ -27,12 +27,14 @@ use tui::store;
use tui::task::{Process, Task};
use tui::ui;
use tui::ui::layout::Spacing;
+
use tui::ui::theme::Theme;
use tui::ui::widget::{
    Borders, Column, ContainerState, TableState, TextEditState, TextViewState, Window,
};
use tui::ui::{BufferedValue, Show, Ui};
use tui::{Channel, Exit};

+
use crate::settings;
use crate::ui::items::filter::Filter;
use crate::ui::items::notification::filter::{NotificationFilter, SortBy};
use crate::ui::items::notification::{Notification, NotificationKind};
@@ -163,6 +165,7 @@ pub struct AppState {
    filter: NotificationFilter,
    loading: bool,
    initialized: bool,
+
    theme: Theme,
}

#[derive(Clone, Debug)]
@@ -176,6 +179,9 @@ impl TryFrom<&Context> for App {
    type Error = anyhow::Error;

    fn try_from(context: &Context) -> Result<Self, Self::Error> {
+
        let settings = settings::Settings::default();
+
        let theme = settings::configure_theme(&settings);
+

        let search = context.search.as_ref().map(|s| s.trim().to_string());
        let (search, filter) = match search {
            Some(search) => (
@@ -204,6 +210,7 @@ impl TryFrom<&Context> for App {
                filter,
                loading: false,
                initialized: false,
+
                theme,
            },
        })
    }
@@ -288,7 +295,7 @@ impl store::Update<Message> for App {

impl Show<Message> for App {
    fn show(&self, ctx: &ui::Context<Message>, frame: &mut Frame) -> Result<()> {
-
        Window::default().show(ctx, |ui| {
+
        Window::default().show(ctx, self.state.theme.clone(), |ui| {
            // Initialize
            if !self.state.initialized {
                ui.send_message(Message::Initialize);
modified bin/commands/issue.rs
@@ -5,8 +5,6 @@ use std::ffi::OsString;

use anyhow::anyhow;

-
use lazy_static::lazy_static;
-

use radicle::cob::thread::CommentId;
use radicle::identity::RepoId;
use radicle::issue::{IssueId, State};
@@ -23,13 +21,6 @@ use crate::commands::tui_issue::list::IssueOperation;
use crate::terminal;
use crate::ui::items::filter::DidFilter;
use crate::ui::items::issue::filter::IssueFilter;
-
use crate::ui::TerminalInfo;
-

-
lazy_static! {
-
    static ref TERMINAL_INFO: TerminalInfo = TerminalInfo {
-
        luma: Some(terminal_light::luma().unwrap_or_default())
-
    };
-
}

pub const HELP: Help = Help {
    name: "issue",
@@ -243,8 +234,6 @@ pub async fn run(options: Options, ctx: impl Context) -> anyhow::Result<()> {
    let (_, rid) = radicle::rad::cwd()
        .map_err(|_| anyhow!("this command must be run in the context of a project"))?;

-
    let terminal_info = TERMINAL_INFO.clone();
-

    match options.op {
        Operation::List { opts } => {
            if let Err(err) = crate::log::enable() {
@@ -278,7 +267,7 @@ pub async fn run(options: Options, ctx: impl Context) -> anyhow::Result<()> {
                    comment: state.comment_id,
                };

-
                let tui = list::Tui::new(context, terminal_info.clone());
+
                let tui = list::Tui::new(context);
                let selection = tui.run().await?;

                if opts.json {
modified bin/commands/issue/list.rs
@@ -25,6 +25,7 @@ use tui::task::EmptyProcessors;
use tui::ui;
use tui::ui::layout::Spacing;
use tui::ui::span;
+
use tui::ui::theme::Theme;
use tui::ui::widget::{
    Borders, Column, ContainerState, TableState, TextEditState, TextViewState, TreeState, Window,
};
@@ -32,12 +33,12 @@ use tui::ui::{BufferedValue, Show, ToRow, Ui};
use tui::{Channel, Exit};

use crate::cob::issue;
-
use crate::settings::{self, ThemeBundle, ThemeMode};
+
use crate::settings;
+
use crate::ui::format;
use crate::ui::items::filter::Filter;
use crate::ui::items::issue::filter::IssueFilter;
use crate::ui::items::issue::Issue;
use crate::ui::items::HasId;
-
use crate::ui::{format, TerminalInfo};

type Selection = tui::Selection<IssueOperation>;

@@ -135,21 +136,17 @@ pub struct Context {

pub(crate) struct Tui {
    pub(crate) context: Context,
-
    pub(crate) terminal_info: TerminalInfo,
}

impl Tui {
-
    pub fn new(context: Context, terminal_info: TerminalInfo) -> Self {
-
        Self {
-
            context,
-
            terminal_info,
-
        }
+
    pub fn new(context: Context) -> Self {
+
        Self { context }
    }

    pub async fn run(&self) -> Result<Option<Selection>> {
        let viewport = Viewport::Inline(20);
        let channel = Channel::default();
-
        let state = App::try_from((&self.context, &self.terminal_info))?;
+
        let state = App::try_from(&self.context)?;

        tui::im(state, viewport, channel, EmptyProcessors::new()).await
    }
@@ -292,6 +289,7 @@ pub struct AppState {
    preview: args::Preview,
    help: TextViewState,
    filter: IssueFilter,
+
    theme: Theme,
}

#[derive(Clone, Debug)]
@@ -300,12 +298,12 @@ pub struct App {
    state: AppState,
}

-
impl TryFrom<(&Context, &TerminalInfo)> for App {
+
impl TryFrom<&Context> for App {
    type Error = anyhow::Error;

-
    fn try_from(value: (&Context, &TerminalInfo)) -> Result<Self, Self::Error> {
-
        let (context, terminal_info) = value;
+
    fn try_from(context: &Context) -> Result<Self, Self::Error> {
        let settings = settings::Settings::default();
+
        let theme = settings::configure_theme(&settings);

        let issues = issue::all(&context.profile, &context.repository)?;
        let search = context.search.as_ref().map(|s| s.trim().to_string());
@@ -320,20 +318,6 @@ impl TryFrom<(&Context, &TerminalInfo)> for App {
            }
        };

-
        let default_bundle = ThemeBundle::default();
-
        let theme_bundle = settings.theme.active_bundle().unwrap_or(&default_bundle);
-
        let _theme = match settings.theme.mode() {
-
            ThemeMode::Auto => {
-
                if terminal_info.is_dark() {
-
                    theme_bundle.dark.clone()
-
                } else {
-
                    theme_bundle.light.clone()
-
                }
-
            }
-
            ThemeMode::Light => theme_bundle.light.clone(),
-
            ThemeMode::Dark => theme_bundle.dark.clone(),
-
        };
-

        // Convert into UI items
        let mut issues: Vec<_> = issues
            .into_iter()
@@ -413,6 +397,7 @@ impl TryFrom<(&Context, &TerminalInfo)> for App {
                preview,
                filter,
                help: TextViewState::new(Position::default()),
+
                theme,
            },
        })
    }
@@ -544,7 +529,7 @@ impl store::Update<Message> for App {

impl Show<Message> for App {
    fn show(&self, ctx: &ui::Context<Message>, frame: &mut Frame) -> Result<()> {
-
        Window::default().show(ctx, |ui| {
+
        Window::default().show(ctx, self.state.theme.clone(), |ui| {
            match self.state.page.clone() {
                args::Page::Main => {
                    let show_search = self.state.browser.show_search;
modified bin/commands/patch/list.rs
@@ -24,12 +24,14 @@ use tui::store;
use tui::task::{Process, Task};
use tui::ui;
use tui::ui::layout::Spacing;
+
use tui::ui::theme::Theme;
use tui::ui::widget::{
    Borders, Column, ContainerState, TableState, TextEditState, TextViewState, Window,
};
use tui::ui::{BufferedValue, Show, Ui};
use tui::{Channel, Exit};

+
use crate::settings;
use crate::ui::items::filter::Filter;
use crate::ui::items::patch::filter::PatchFilter;
use crate::ui::items::patch::Patch;
@@ -236,6 +238,7 @@ pub struct AppState {
    filter: PatchFilter,
    loading: bool,
    initialized: bool,
+
    theme: Theme,
}

#[derive(Clone, Debug)]
@@ -248,6 +251,9 @@ impl TryFrom<&Context> for App {
    type Error = anyhow::Error;

    fn try_from(context: &Context) -> Result<Self, Self::Error> {
+
        let settings = settings::Settings::default();
+
        let theme = settings::configure_theme(&settings);
+

        let repo = &context.profile.storage.repository(context.rid)?;
        let cache = &context.profile.patches(repo)?;
        let mut patches = cache
@@ -294,6 +300,7 @@ impl TryFrom<&Context> for App {
                filter,
                loading: false,
                initialized: false,
+
                theme,
            },
        })
    }
@@ -377,7 +384,7 @@ impl store::Update<Message> for App {

impl Show<Message> for App {
    fn show(&self, ctx: &ui::Context<Message>, frame: &mut Frame) -> Result<()> {
-
        Window::default().show(ctx, |ui| {
+
        Window::default().show(ctx, self.state.theme.clone(), |ui| {
            // Initialize
            if !self.state.initialized {
                ui.send_message(Message::Initialize);
modified bin/commands/patch/review.rs
@@ -25,11 +25,13 @@ use tui::store;
use tui::task::EmptyProcessors;
use tui::ui::layout::Spacing;
use tui::ui::span;
+
use tui::ui::theme::Theme;
use tui::ui::widget::{Borders, Column, ContainerState, TableState, TextViewState, Window};
use tui::ui::{Context, Show, Ui};
use tui::{Channel, Exit};

use crate::git::HunkState;
+
use crate::settings;
use crate::state::{self, FileIdentifier, FileStore, ReadState, WriteState};
use crate::ui::format;
use crate::ui::items::patch::{HunkItem, StatefulHunkItem};
@@ -173,6 +175,8 @@ pub struct AppState {
    views: Vec<DiffViewState>,
    /// State of text view widget on the help page.
    help: TextViewState,
+
    /// The active theme
+
    theme: Theme,
}

impl AppState {
@@ -184,6 +188,9 @@ impl AppState {
        revision: RevisionId,
        hunks: &Hunks,
    ) -> Self {
+
        let settings = settings::Settings::default();
+
        let theme = settings::configure_theme(&settings);
+

        Self {
            mode,
            rid,
@@ -198,6 +205,7 @@ impl AppState {
            ),
            views: vec![DiffViewState::default(); hunks.len()],
            help: TextViewState::new(Position::default()),
+
            theme,
        }
    }

@@ -516,12 +524,12 @@ impl App<'_> {

impl Show<Message> for App<'_> {
    fn show(&self, ctx: &Context<Message>, frame: &mut Frame) -> Result<(), anyhow::Error> {
-
        Window::default().show(ctx, |ui| {
-
            let page = {
-
                let state = self.state.lock().unwrap();
-
                state.page.clone()
-
            };
+
        let (page, theme) = {
+
            let state = self.state.lock().unwrap();
+
            (state.page.clone(), state.theme.clone())
+
        };

+
        Window::default().show(ctx, theme, |ui| {
            match page {
                AppPage::Main => {
                    let (mut focus, count) = {
modified bin/settings.rs
@@ -2,15 +2,35 @@ use std::collections::HashMap;
use std::env;
use std::path::PathBuf;

+
use lazy_static::lazy_static;
+

use thiserror::Error;

use homedir::my_home;

use radicle_tui as tui;
+

use tui::ui::theme::Theme;

static THEME_RADICLE: &str = "Radicle";

+
#[derive(Clone, Debug)]
+
pub struct TerminalInfo {
+
    pub luma: Option<f32>,
+
}
+

+
impl TerminalInfo {
+
    pub fn is_dark(&self) -> bool {
+
        self.luma.unwrap_or_default() <= 0.6
+
    }
+
}
+

+
lazy_static! {
+
    static ref TERMINAL_INFO: TerminalInfo = TerminalInfo {
+
        luma: Some(terminal_light::luma().unwrap_or_default())
+
    };
+
}
+

pub type ThemeBundleId = String;

#[derive(Error, Debug)]
@@ -87,6 +107,23 @@ impl Default for Settings {
    }
}

+
pub fn configure_theme(settings: &Settings) -> Theme {
+
    let default_bundle = ThemeBundle::default();
+
    let theme_bundle = settings.theme.active_bundle().unwrap_or(&default_bundle);
+

+
    match settings.theme.mode() {
+
        ThemeMode::Auto => {
+
            if TERMINAL_INFO.is_dark() {
+
                theme_bundle.dark.clone()
+
            } else {
+
                theme_bundle.light.clone()
+
            }
+
        }
+
        ThemeMode::Light => theme_bundle.light.clone(),
+
        ThemeMode::Dark => theme_bundle.dark.clone(),
+
    }
+
}
+

pub fn get_state_path() -> Result<PathBuf, Error> {
    let base = match env::var("XDG_STATE_HOME") {
        Ok(path) => PathBuf::from(path),
modified bin/ui.rs
@@ -14,17 +14,6 @@ use tui::ui::widget::{Borders, Column, TableState, TextEditState, Widget};
use tui::ui::{BufferedValue, ToRow};
use tui::ui::{Response, Ui};

-
#[derive(Clone, Debug)]
-
pub struct TerminalInfo {
-
    pub luma: Option<f32>,
-
}
-

-
impl TerminalInfo {
-
    pub fn is_dark(&self) -> bool {
-
        self.luma.unwrap_or_default() <= 0.6
-
    }
-
}
-

pub struct UiExt<'a, M>(&'a mut Ui<M>);

impl<'a, M> UiExt<'a, M> {