-
-
Notifications
You must be signed in to change notification settings - Fork 188
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(macOS): support progress bar on dock (#766)
Co-authored-by: Lucas Nogueira <[email protected]>
- Loading branch information
1 parent
4233d26
commit 3b7e0d9
Showing
9 changed files
with
276 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Copyright 2014-2021 The winit contributors | ||
// Copyright 2021-2023 Tauri Programme within The Commons Conservancy | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use tao::{ | ||
event::{ElementState, Event, KeyEvent, WindowEvent}, | ||
event_loop::{ControlFlow, EventLoop}, | ||
keyboard::{Key, ModifiersState}, | ||
window::{ProgressBarState, ProgressState, WindowBuilder}, | ||
}; | ||
|
||
#[allow(clippy::single_match)] | ||
fn main() { | ||
env_logger::init(); | ||
let event_loop = EventLoop::new(); | ||
|
||
let window = WindowBuilder::new().build(&event_loop).unwrap(); | ||
|
||
let mut modifiers = ModifiersState::default(); | ||
|
||
eprintln!("Key mappings:"); | ||
eprintln!(" [1-5]: Set progress to [0%, 25%, 50%, 75%, 100%]"); | ||
eprintln!(" Ctrl+1: Set state to None"); | ||
eprintln!(" Ctrl+2: Set state to Normal"); | ||
eprintln!(" Ctrl+3: Set state to Indeterminate"); | ||
eprintln!(" Ctrl+4: Set state to Paused"); | ||
eprintln!(" Ctrl+5: Set state to Error"); | ||
|
||
event_loop.run(move |event, _, control_flow| { | ||
*control_flow = ControlFlow::Wait; | ||
|
||
match event { | ||
Event::WindowEvent { | ||
event: WindowEvent::CloseRequested, | ||
.. | ||
} => *control_flow = ControlFlow::Exit, | ||
Event::WindowEvent { event, .. } => match event { | ||
WindowEvent::ModifiersChanged(new_state) => { | ||
modifiers = new_state; | ||
} | ||
WindowEvent::KeyboardInput { | ||
event: | ||
KeyEvent { | ||
logical_key: Key::Character(key_str), | ||
state: ElementState::Released, | ||
.. | ||
}, | ||
.. | ||
} => { | ||
if modifiers.is_empty() { | ||
let mut progress: u64 = 0; | ||
match key_str { | ||
"1" => progress = 0, | ||
"2" => progress = 25, | ||
"3" => progress = 50, | ||
"4" => progress = 75, | ||
"5" => progress = 100, | ||
_ => {} | ||
} | ||
|
||
window.set_progress_bar(ProgressBarState { | ||
progress: Some(progress), | ||
state: Some(ProgressState::Normal), | ||
unity_uri: None, | ||
}); | ||
} else if modifiers.control_key() { | ||
let mut state = ProgressState::None; | ||
match key_str { | ||
"1" => state = ProgressState::None, | ||
"2" => state = ProgressState::Normal, | ||
"3" => state = ProgressState::Indeterminate, | ||
"4" => state = ProgressState::Paused, | ||
"5" => state = ProgressState::Error, | ||
_ => {} | ||
} | ||
|
||
window.set_progress_bar(ProgressBarState { | ||
progress: None, | ||
state: Some(state), | ||
unity_uri: None, | ||
}); | ||
} | ||
} | ||
_ => {} | ||
}, | ||
_ => {} | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
use std::sync::Once; | ||
|
||
use cocoa::{ | ||
base::{id, nil}, | ||
foundation::{NSArray, NSPoint, NSRect, NSSize}, | ||
}; | ||
use objc::{ | ||
declare::ClassDecl, | ||
runtime::{Class, Object, Sel, NO}, | ||
}; | ||
|
||
use crate::window::{ProgressBarState, ProgressState}; | ||
|
||
/// Set progress indicator in the Dock. | ||
pub fn set_progress_indicator(progress_state: ProgressBarState) { | ||
unsafe { | ||
let ns_app: id = msg_send![class!(NSApplication), sharedApplication]; | ||
let dock_tile: id = msg_send![ns_app, dockTile]; | ||
if dock_tile == nil { | ||
return; | ||
} | ||
|
||
// check progress indicator is already set or create new one | ||
let progress_indicator: id = get_exist_progress_indicator(dock_tile) | ||
.unwrap_or_else(|| create_progress_indicator(ns_app, dock_tile)); | ||
|
||
// set progress indicator state | ||
if let Some(progress) = progress_state.progress { | ||
let progress = progress.clamp(0, 100) as f64; | ||
let _: () = msg_send![progress_indicator, setDoubleValue: progress]; | ||
let _: () = msg_send![progress_indicator, setHidden: NO]; | ||
} | ||
if let Some(state) = progress_state.state { | ||
(*progress_indicator).set_ivar("state", state as u8); | ||
let _: () = msg_send![ | ||
progress_indicator, | ||
setHidden: matches!(state, ProgressState::None) | ||
]; | ||
} | ||
|
||
let _: () = msg_send![dock_tile, display]; | ||
} | ||
} | ||
|
||
fn create_progress_indicator(ns_app: id, dock_tile: id) -> id { | ||
unsafe { | ||
let mut image_view: id = msg_send![dock_tile, contentView]; | ||
if image_view == nil { | ||
// create new dock tile view with current app icon | ||
let app_icon_image: id = msg_send![ns_app, applicationIconImage]; | ||
image_view = msg_send![class!(NSImageView), imageViewWithImage: app_icon_image]; | ||
let _: () = msg_send![dock_tile, setContentView: image_view]; | ||
} | ||
|
||
// create custom progress indicator | ||
let dock_tile_size: NSSize = msg_send![dock_tile, size]; | ||
let frame = NSRect::new( | ||
NSPoint::new(0.0, 0.0), | ||
NSSize::new(dock_tile_size.width, 15.0), | ||
); | ||
let progress_class = create_progress_indicator_class(); | ||
let progress_indicator: id = msg_send![progress_class, alloc]; | ||
let progress_indicator: id = msg_send![progress_indicator, initWithFrame: frame]; | ||
let _: () = msg_send![progress_indicator, autorelease]; | ||
|
||
// set progress indicator to the dock tile | ||
let _: () = msg_send![image_view, addSubview: progress_indicator]; | ||
|
||
progress_indicator | ||
} | ||
} | ||
|
||
fn get_exist_progress_indicator(dock_tile: id) -> Option<id> { | ||
unsafe { | ||
let content_view: id = msg_send![dock_tile, contentView]; | ||
if content_view == nil { | ||
return None; | ||
} | ||
let subviews: id /* NSArray */ = msg_send![content_view, subviews]; | ||
if subviews == nil { | ||
return None; | ||
} | ||
|
||
for idx in 0..subviews.count() { | ||
let subview: id = msg_send![subviews, objectAtIndex: idx]; | ||
|
||
let is_progress_indicator: bool = | ||
msg_send![subview, isKindOfClass: class!(NSProgressIndicator)]; | ||
if is_progress_indicator { | ||
return Some(subview); | ||
} | ||
} | ||
} | ||
None | ||
} | ||
|
||
fn create_progress_indicator_class() -> *const Class { | ||
static mut APP_CLASS: *const Class = 0 as *const Class; | ||
static INIT: Once = Once::new(); | ||
|
||
INIT.call_once(|| unsafe { | ||
let superclass = class!(NSProgressIndicator); | ||
let mut decl = ClassDecl::new("TaoProgressIndicator", superclass).unwrap(); | ||
|
||
decl.add_method( | ||
sel!(drawRect:), | ||
draw_progress_bar as extern "C" fn(&Object, _, NSRect), | ||
); | ||
|
||
// progress bar states, follows ProgressState | ||
decl.add_ivar::<u8>("state"); | ||
|
||
APP_CLASS = decl.register(); | ||
}); | ||
|
||
unsafe { APP_CLASS } | ||
} | ||
|
||
extern "C" fn draw_progress_bar(this: &Object, _: Sel, rect: NSRect) { | ||
unsafe { | ||
let bar = NSRect::new( | ||
NSPoint { x: 0.0, y: 4.0 }, | ||
NSSize { | ||
width: rect.size.width, | ||
height: 8.0, | ||
}, | ||
); | ||
let bar_inner = bar.inset(0.5, 0.5); | ||
let mut bar_progress = bar.inset(1.0, 1.0); | ||
|
||
// set progress width | ||
let current_progress: f64 = msg_send![this, doubleValue]; | ||
let normalized_progress: f64 = (current_progress / 100.0).clamp(0.0, 1.0); | ||
bar_progress.size.width *= normalized_progress; | ||
|
||
// draw outer bar | ||
let bg_color: id = msg_send![class!(NSColor), colorWithWhite:1.0 alpha:0.05]; | ||
let _: () = msg_send![bg_color, set]; | ||
draw_rounded_rect(bar); | ||
// draw inner bar | ||
draw_rounded_rect(bar_inner); | ||
|
||
// draw progress | ||
let state: u8 = *(this.get_ivar("state")); | ||
let progress_color: id = match state { | ||
x if x == ProgressState::Paused as u8 => msg_send![class!(NSColor), systemYellowColor], | ||
x if x == ProgressState::Error as u8 => msg_send![class!(NSColor), systemRedColor], | ||
_ => msg_send![class!(NSColor), systemBlueColor], | ||
}; | ||
let _: () = msg_send![progress_color, set]; | ||
draw_rounded_rect(bar_progress); | ||
} | ||
} | ||
|
||
fn draw_rounded_rect(rect: NSRect) { | ||
unsafe { | ||
let raduis = rect.size.height / 2.0; | ||
let bezier_path: id = | ||
msg_send![class!(NSBezierPath), bezierPathWithRoundedRect:rect xRadius:raduis yRadius:raduis]; | ||
let _: () = msg_send![bezier_path, fill]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters