Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kiosk mode #533

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions psst-gui/src/data/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ pub struct Config {
pub sort_criteria: SortCriteria,
pub paginated_limit: usize,
pub seek_duration: usize,
pub kiosk_mode: bool,
}

impl Default for Config {
Expand All @@ -118,6 +119,7 @@ impl Default for Config {
sort_criteria: Default::default(),
paginated_limit: 500,
seek_duration: 10,
kiosk_mode: false,
}
}
}
Expand Down
32 changes: 21 additions & 11 deletions psst-gui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod widget;

use druid::AppLauncher;
use env_logger::{Builder, Env};
use std::env;
use webapi::WebApi;

use crate::{
Expand All @@ -33,32 +34,41 @@ fn main() {

let config = Config::load().unwrap_or_default();
let paginated_limit = config.paginated_limit;
let state = AppState::default_with_config(config);
let mut state = AppState::default_with_config(config);

let args: Vec<String> = env::args().collect();
state.config.kiosk_mode = args.iter().any(|arg| arg == "-k" || arg == "--kiosk");

WebApi::new(
state.session.clone(),
Config::proxy().as_deref(),
Config::cache_dir(),
paginated_limit,
)
.install_as_global();

let delegate;
let launcher;
if state.config.has_credentials() {
let (delegate, launcher) = if state.config.has_credentials() {
// Credentials are configured, open the main window.
let window = ui::main_window(&state.config);
delegate = Delegate::with_main(window.id);
launcher = AppLauncher::with_window(window).configure_env(ui::theme::setup);
let delegate = Delegate::with_main(window.id);

// Load user's local tracks for the WebApi.
WebApi::global().load_local_tracks(state.config.username().unwrap());

(delegate, AppLauncher::with_window(window))
} else {
// No configured credentials, open the account setup.
let window = ui::account_setup_window();
delegate = Delegate::with_preferences(window.id);
launcher = AppLauncher::with_window(window).configure_env(ui::theme::setup);
// No configured credentials, open the setup window.
let window = if state.config.kiosk_mode {
ui::kiosk_setup_window()
} else {
ui::account_setup_window()
};
let delegate = Delegate::with_preferences(window.id);

(delegate, AppLauncher::with_window(window))
};

let launcher = launcher.configure_env(ui::theme::setup);

launcher
.delegate(delegate)
.launch(state)
Expand Down
4 changes: 2 additions & 2 deletions psst-gui/src/ui/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::{
data::{AppState, Nav},
};

pub fn main_menu(_window: Option<WindowId>, _data: &AppState, _env: &Env) -> Menu<AppState> {
if cfg!(target_os = "macos") {
pub fn main_menu(_window: Option<WindowId>, data: &AppState, _env: &Env) -> Menu<AppState> {
if cfg!(target_os = "macos") && !data.config.kiosk_mode {
Menu::empty().entry(mac_app_menu())
} else {
Menu::empty()
Expand Down
58 changes: 50 additions & 8 deletions psst-gui/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use druid::{
im::Vector,
widget::{CrossAxisAlignment, Either, Flex, Label, List, Scroll, Slider, Split, ViewSwitcher},
Color, Env, Insets, Key, LensExt, Menu, MenuItem, Selector, Widget, WidgetExt, WindowDesc,
WindowState,
};
use druid_shell::Cursor;

Expand Down Expand Up @@ -43,12 +44,30 @@ pub mod user;
pub mod utils;

pub fn main_window(config: &Config) -> WindowDesc<AppState> {
let win = WindowDesc::new(root_widget())
let mut win = WindowDesc::new(root_widget(config))
.title(compute_main_window_title)
.with_min_size((theme::grid(65.0), theme::grid(50.0)))
.window_size(config.window_size)
.show_title(false)
.transparent_titlebar(true);
.show_title(false);

if config.kiosk_mode {
win = win
.set_window_state(WindowState::Maximized)
.resizable(false)
.show_titlebar(false);

// Set the window size to the primary monitor's work area and position it at (0, 0)
if let Some(monitor) = druid::Screen::get_monitors().first() {
let work_area = monitor.virtual_work_rect();
win = win
.window_size(work_area.size())
.set_position(druid::Point::new(0.0, 0.0));
}
} else {
win = win
.window_size(config.window_size)
.with_min_size((theme::grid(65.0), theme::grid(50.0)))
.transparent_titlebar(true);
}

if cfg!(target_os = "macos") {
win.menu(menu::main_menu)
} else {
Expand All @@ -72,6 +91,7 @@ pub fn preferences_window() -> WindowDesc<AppState> {
.window_size(win_size)
.resizable(false)
.show_title(false)
.set_always_on_top(true)
.transparent_titlebar(true);
if cfg!(target_os = "macos") {
win.menu(menu::main_menu)
Expand All @@ -83,9 +103,23 @@ pub fn preferences_window() -> WindowDesc<AppState> {
pub fn account_setup_window() -> WindowDesc<AppState> {
let win = WindowDesc::new(account_setup_widget())
.title("Login")
.resizable(false)
.show_title(false)
.window_size((theme::grid(50.0), theme::grid(45.0)))
.transparent_titlebar(true);
if cfg!(target_os = "macos") {
win.menu(menu::main_menu)
} else {
win
}
}

pub fn kiosk_setup_window() -> WindowDesc<AppState> {
let win = WindowDesc::new(kiosk_setup_widget())
.title("Setup")
.resizable(false)
.show_title(false)
.window_size((theme::grid(50.0), theme::grid(45.0)))
.transparent_titlebar(true);
if cfg!(target_os = "macos") {
win.menu(menu::main_menu)
Expand All @@ -110,7 +144,15 @@ fn account_setup_widget() -> impl Widget<AppState> {
)
}

fn root_widget() -> impl Widget<AppState> {
fn kiosk_setup_widget() -> impl Widget<AppState> {
ThemeScope::new(
preferences::kiosk_setup_widget()
.background(theme::BACKGROUND_DARK)
.expand(),
)
}

fn root_widget(config: &Config) -> impl Widget<AppState> {
let playlists = Scroll::new(playlist::list_widget())
.vertical()
.expand_height();
Expand All @@ -120,8 +162,8 @@ fn root_widget() -> impl Widget<AppState> {
.with_child(sidebar_menu_widget())
.with_default_spacer()
.with_flex_child(playlists, 1.0)
.padding(if cfg!(target_os = "macos") {
// Accommodate the window controls on Mac.
.padding(if cfg!(target_os = "macos") && !config.kiosk_mode {
// Accommodate the window controls on macOS
Insets::new(0.0, 24.0, 0.0, 0.0)
SO9010 marked this conversation as resolved.
Show resolved Hide resolved
} else {
Insets::ZERO
Expand Down
60 changes: 57 additions & 3 deletions psst-gui/src/ui/preferences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,29 @@ pub fn account_setup_widget() -> impl Widget<AppState> {
.padding(theme::grid(4.0))
}

pub fn kiosk_setup_widget() -> impl Widget<AppState> {
Flex::column()
.must_fill_main_axis(true)
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_spacer(theme::grid(2.0))
.with_child(
Label::new("Please insert your Spotify Premium credentials.")
.with_font(theme::UI_FONT_MEDIUM)
.with_line_break_mode(LineBreaking::WordWrap),
)
.with_spacer(theme::grid(2.0))
.with_child(
Label::new(
"Psst connects only to the official servers, and does not store your password.",
)
.with_text_color(theme::PLACEHOLDER_COLOR)
.with_line_break_mode(LineBreaking::WordWrap),
)
.with_spacer(theme::grid(6.0))
.with_child(account_tab_widget(AccountTab::KioskSetup).expand_width())
.padding(theme::grid(4.0))
}

pub fn preferences_widget() -> impl Widget<AppState> {
const PROPAGATE_FLAGS: Selector = Selector::new("app.preferences.propagate-flags");

Expand Down Expand Up @@ -247,19 +270,27 @@ fn general_tab_widget() -> impl Widget<AppState> {
.lens(AppState::config.then(Config::paginated_limit)),
);

col = col.with_default_spacer().with_child(
Button::new("Done")
.align_right()
.on_click(|ctx, _, _| ctx.submit_command(commands::CLOSE_WINDOW)),
);

col
}

#[derive(Copy, Clone)]
enum AccountTab {
FirstSetup,
InPreferences,
KioskSetup,
}

fn account_tab_widget(tab: AccountTab) -> impl Widget<AppState> {
let mut col = Flex::column().cross_axis_alignment(match tab {
AccountTab::FirstSetup => CrossAxisAlignment::Center,
AccountTab::InPreferences => CrossAxisAlignment::Start,
AccountTab::KioskSetup => CrossAxisAlignment::Start,
});

if matches!(tab, AccountTab::InPreferences) {
Expand Down Expand Up @@ -293,9 +324,16 @@ fn account_tab_widget(tab: AccountTab) -> impl Widget<AppState> {
);

if matches!(tab, AccountTab::InPreferences) {
col = col.with_child(Button::new("Log Out").on_left_click(|ctx, _, _, _| {
ctx.submit_command(cmd::LOG_OUT);
}))
col = col
.with_child(Button::new("Log Out").on_left_click(|ctx, _, _, _| {
ctx.submit_command(cmd::LOG_OUT);
}))
.with_default_spacer()
.with_child(
Button::new("Done")
.align_right()
.on_click(|ctx, _, _| ctx.submit_command(commands::CLOSE_WINDOW)),
)
}

col.controller(Authenticate::new(tab))
Expand Down Expand Up @@ -391,6 +429,11 @@ impl<W: Widget<AppState>> Controller<AppState, W> for Authenticate {
AccountTab::InPreferences => {
ctx.submit_command(cmd::SESSION_CONNECT);
}
AccountTab::KioskSetup => {
ctx.submit_command(commands::CLOSE_WINDOW);
ctx.submit_command(commands::SHOW_PREFERENCES);
ctx.submit_command(cmd::SHOW_MAIN);
}
}
}
data.preferences.auth.access_token.clear();
Expand Down Expand Up @@ -442,6 +485,11 @@ fn cache_tab_widget() -> impl Widget<AppState> {
}
},
));
col = col.with_default_spacer().with_child(
Button::new("Done")
.align_right()
.on_click(|ctx, _, _| ctx.submit_command(commands::CLOSE_WINDOW)),
);

col.controller(MeasureCacheSize::new())
.lens(AppState::preferences)
Expand Down Expand Up @@ -534,4 +582,10 @@ fn about_tab_widget() -> impl Widget<AppState> {
.with_child(commit_hash)
.with_child(build_time)
.with_child(remote_url)
.with_default_spacer()
.with_child(
Button::new("Done")
.align_right()
.on_click(|ctx, _, _| ctx.submit_command(commands::CLOSE_WINDOW)),
)
}
11 changes: 9 additions & 2 deletions psst-gui/src/ui/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use druid::{
use crate::{
data::{AppState, Library, UserProfile},
webapi::WebApi,
widget::{icons, icons::SvgIcon, Async, Empty, MyWidgetExt},
widget::{
icons::{self, SvgIcon},
Async, Empty, MyWidgetExt,
},
};

use super::theme;
Expand Down Expand Up @@ -51,7 +54,11 @@ pub fn user_widget() -> impl Widget<AppState> {
.with_child(user_profile)
.padding(theme::grid(1.0)),
)
.with_child(preferences_widget(&icons::PREFERENCES))
.with_child(Either::new(
|data: &AppState, _| !data.config.kiosk_mode,
preferences_widget(&icons::PREFERENCES),
Empty,
))
}

fn preferences_widget<T: Data>(svg: &SvgIcon) -> impl Widget<T> {
Expand Down