Implement installation wizard for comet in devtools
This commit is contained in:
parent
00ee6ab47a
commit
5c39cd4478
3 changed files with 274 additions and 64 deletions
|
|
@ -5,12 +5,14 @@ use crate::core::window;
|
||||||
|
|
||||||
pub use internal::Span;
|
pub use internal::Span;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
|
||||||
pub fn init(name: &str) {
|
pub fn init(name: &str) {
|
||||||
internal::init(name);
|
internal::init(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_comet() {
|
pub fn toggle_comet() -> Result<(), io::Error> {
|
||||||
internal::toggle_comet();
|
internal::toggle_comet()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn theme_changed(f: impl FnOnce() -> Option<theme::Palette>) {
|
pub fn theme_changed(f: impl FnOnce() -> Option<theme::Palette>) {
|
||||||
|
|
@ -72,6 +74,7 @@ mod internal {
|
||||||
use beacon::client::{self, Client};
|
use beacon::client::{self, Client};
|
||||||
use beacon::span;
|
use beacon::span;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::atomic::{self, AtomicBool};
|
use std::sync::atomic::{self, AtomicBool};
|
||||||
use std::sync::{LazyLock, RwLock};
|
use std::sync::{LazyLock, RwLock};
|
||||||
|
|
@ -82,21 +85,25 @@ mod internal {
|
||||||
name.clone_into(&mut NAME.write().expect("Write application name"));
|
name.clone_into(&mut NAME.write().expect("Write application name"));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_comet() {
|
pub fn toggle_comet() -> Result<(), io::Error> {
|
||||||
if BEACON.is_connected() {
|
if BEACON.is_connected() {
|
||||||
BEACON.quit();
|
BEACON.quit();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
let _ = process::Command::new("iced_comet")
|
let _ = process::Command::new("iced_comet")
|
||||||
.stdin(process::Stdio::null())
|
.stdin(process::Stdio::null())
|
||||||
.stdout(process::Stdio::null())
|
.stdout(process::Stdio::null())
|
||||||
.stderr(process::Stdio::null())
|
.stderr(process::Stdio::null())
|
||||||
.spawn();
|
.spawn()?;
|
||||||
|
|
||||||
if let Some(palette) =
|
if let Some(palette) =
|
||||||
LAST_PALETTE.read().expect("Read last palette").as_ref()
|
LAST_PALETTE.read().expect("Read last palette").as_ref()
|
||||||
{
|
{
|
||||||
BEACON.log(client::Event::ThemeChanged(*palette));
|
BEACON.log(client::Event::ThemeChanged(*palette));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -209,9 +216,13 @@ mod internal {
|
||||||
use crate::core::theme;
|
use crate::core::theme;
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
|
||||||
pub fn init(_name: &str) {}
|
pub fn init(_name: &str) {}
|
||||||
|
|
||||||
pub fn toggle_comet() {}
|
pub fn toggle_comet() -> Result<(), io::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn theme_changed(_f: impl FnOnce() -> Option<theme::Palette>) {}
|
pub fn theme_changed(_f: impl FnOnce() -> Option<theme::Palette>) {}
|
||||||
|
|
||||||
|
|
|
||||||
19
devtools/src/executor.rs
Normal file
19
devtools/src/executor.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
use crate::futures::futures::channel::mpsc;
|
||||||
|
use crate::runtime::Task;
|
||||||
|
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
pub fn spawn_blocking<T>(
|
||||||
|
f: impl FnOnce(mpsc::Sender<T>) + Send + 'static,
|
||||||
|
) -> Task<T>
|
||||||
|
where
|
||||||
|
T: Send + 'static,
|
||||||
|
{
|
||||||
|
let (sender, receiver) = mpsc::channel(1);
|
||||||
|
|
||||||
|
let _ = thread::spawn(move || {
|
||||||
|
f(sender);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task::stream(receiver)
|
||||||
|
}
|
||||||
|
|
@ -6,18 +6,23 @@ use iced_widget::core;
|
||||||
use iced_widget::runtime;
|
use iced_widget::runtime;
|
||||||
use iced_widget::runtime::futures;
|
use iced_widget::runtime::futures;
|
||||||
|
|
||||||
use crate::core::Element;
|
mod executor;
|
||||||
|
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::theme::{self, Base, Theme};
|
use crate::core::theme::{self, Base, Theme};
|
||||||
use crate::core::time::seconds;
|
use crate::core::time::seconds;
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
|
use crate::core::{Color, Element, Length::Fill};
|
||||||
use crate::futures::Subscription;
|
use crate::futures::Subscription;
|
||||||
use crate::futures::futures::channel::oneshot;
|
|
||||||
use crate::program::Program;
|
use crate::program::Program;
|
||||||
use crate::runtime::Task;
|
use crate::runtime::Task;
|
||||||
use crate::widget::{bottom_right, container, stack, text, themer};
|
use crate::widget::{
|
||||||
|
bottom_right, button, center, column, container, horizontal_space, row,
|
||||||
|
scrollable, stack, text, themer,
|
||||||
|
};
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
pub fn attach(program: impl Program + 'static) -> impl Program {
|
pub fn attach(program: impl Program + 'static) -> impl Program {
|
||||||
|
|
@ -30,7 +35,7 @@ pub fn attach(program: impl Program + 'static) -> impl Program {
|
||||||
P: Program + 'static,
|
P: Program + 'static,
|
||||||
{
|
{
|
||||||
type State = DevTools<P>;
|
type State = DevTools<P>;
|
||||||
type Message = Message<P>;
|
type Message = Event<P>;
|
||||||
type Theme = P::Theme;
|
type Theme = P::Theme;
|
||||||
type Renderer = P::Renderer;
|
type Renderer = P::Renderer;
|
||||||
type Executor = P::Executor;
|
type Executor = P::Executor;
|
||||||
|
|
@ -43,7 +48,13 @@ pub fn attach(program: impl Program + 'static) -> impl Program {
|
||||||
let (state, boot) = self.program.boot();
|
let (state, boot) = self.program.boot();
|
||||||
let (devtools, task) = DevTools::new(state);
|
let (devtools, task) = DevTools::new(state);
|
||||||
|
|
||||||
(devtools, Task::batch([boot.map(Message::Program), task]))
|
(
|
||||||
|
devtools,
|
||||||
|
Task::batch([
|
||||||
|
boot.map(Event::Program),
|
||||||
|
task.map(Event::Message),
|
||||||
|
]),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
|
|
@ -102,32 +113,46 @@ where
|
||||||
P: Program,
|
P: Program,
|
||||||
{
|
{
|
||||||
state: P::State,
|
state: P::State,
|
||||||
|
mode: Mode,
|
||||||
show_notification: bool,
|
show_notification: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
HideNotification,
|
||||||
|
ToggleComet,
|
||||||
|
InstallComet,
|
||||||
|
InstallationLogged(String),
|
||||||
|
InstallationFinished,
|
||||||
|
CancelSetup,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
None,
|
||||||
|
Setup(Setup),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Setup {
|
||||||
|
Idle,
|
||||||
|
Running { logs: Vec<String> },
|
||||||
|
}
|
||||||
|
|
||||||
impl<P> DevTools<P>
|
impl<P> DevTools<P>
|
||||||
where
|
where
|
||||||
P: Program + 'static,
|
P: Program + 'static,
|
||||||
{
|
{
|
||||||
pub fn new(state: P::State) -> (Self, Task<Message<P>>) {
|
pub fn new(state: P::State) -> (Self, Task<Message>) {
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
state,
|
state,
|
||||||
|
mode: Mode::None,
|
||||||
show_notification: true,
|
show_notification: true,
|
||||||
},
|
},
|
||||||
Task::perform(
|
executor::spawn_blocking(|mut sender| {
|
||||||
async move {
|
|
||||||
let (sender, receiver) = oneshot::channel();
|
|
||||||
|
|
||||||
let _ = thread::spawn(|| {
|
|
||||||
thread::sleep(seconds(2));
|
thread::sleep(seconds(2));
|
||||||
let _ = sender.send(());
|
let _ = sender.try_send(());
|
||||||
});
|
})
|
||||||
|
.map(|_| Message::HideNotification),
|
||||||
let _ = receiver.await;
|
|
||||||
},
|
|
||||||
|_| Message::HideNotification,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,25 +160,96 @@ where
|
||||||
program.title(&self.state, window)
|
program.title(&self.state, window)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(
|
pub fn update(&mut self, program: &P, event: Event<P>) -> Task<Event<P>> {
|
||||||
&mut self,
|
match event {
|
||||||
program: &P,
|
Event::Message(message) => match message {
|
||||||
message: Message<P>,
|
|
||||||
) -> Task<Message<P>> {
|
|
||||||
match message {
|
|
||||||
Message::HideNotification => {
|
Message::HideNotification => {
|
||||||
self.show_notification = false;
|
self.show_notification = false;
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::ToggleComet => {
|
Message::ToggleComet => {
|
||||||
debug::toggle_comet();
|
if let Mode::Setup(setup) = &self.mode {
|
||||||
|
if matches!(setup, Setup::Idle) {
|
||||||
|
self.mode = Mode::None;
|
||||||
|
}
|
||||||
|
} else if let Err(error) = debug::toggle_comet() {
|
||||||
|
if error.kind() == io::ErrorKind::NotFound {
|
||||||
|
self.mode = Mode::Setup(Setup::Idle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Program(message) => program
|
Message::InstallComet => {
|
||||||
.update(&mut self.state, message)
|
self.mode =
|
||||||
.map(Message::Program),
|
Mode::Setup(Setup::Running { logs: Vec::new() });
|
||||||
|
|
||||||
|
executor::spawn_blocking(|mut sender| {
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
let Ok(install) = Command::new("cargo")
|
||||||
|
.args([
|
||||||
|
"install",
|
||||||
|
"--locked",
|
||||||
|
"--git",
|
||||||
|
"https://github.com/iced-rs/comet.git",
|
||||||
|
])
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut stderr = BufReader::new(
|
||||||
|
install.stderr.expect("stderr must be piped"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut log = String::new();
|
||||||
|
|
||||||
|
while let Ok(n) = stderr.read_line(&mut log) {
|
||||||
|
if n == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = sender.try_send(
|
||||||
|
Message::InstallationLogged(log.clone()),
|
||||||
|
);
|
||||||
|
|
||||||
|
log.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = sender.try_send(Message::InstallationFinished);
|
||||||
|
})
|
||||||
|
.map(Event::Message)
|
||||||
|
}
|
||||||
|
Message::InstallationLogged(log) => {
|
||||||
|
if let Mode::Setup(Setup::Running { logs }) = &mut self.mode
|
||||||
|
{
|
||||||
|
logs.push(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::InstallationFinished => {
|
||||||
|
self.mode = Mode::None;
|
||||||
|
|
||||||
|
let _ = debug::toggle_comet();
|
||||||
|
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::CancelSetup => {
|
||||||
|
self.mode = Mode::None;
|
||||||
|
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::Program(message) => {
|
||||||
|
program.update(&mut self.state, message).map(Event::Program)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,30 +257,118 @@ where
|
||||||
&self,
|
&self,
|
||||||
program: &P,
|
program: &P,
|
||||||
window: window::Id,
|
window: window::Id,
|
||||||
) -> Element<'_, Message<P>, P::Theme, P::Renderer> {
|
) -> Element<'_, Event<P>, P::Theme, P::Renderer> {
|
||||||
let view = program.view(&self.state, window).map(Message::Program);
|
let view = program.view(&self.state, window).map(Event::Program);
|
||||||
let theme = program.theme(&self.state, window);
|
let theme = program.theme(&self.state, window);
|
||||||
|
|
||||||
let notification = themer(
|
let derive_theme = move || {
|
||||||
theme
|
theme
|
||||||
.palette()
|
.palette()
|
||||||
.map(|palette| Theme::custom("DevTools".to_owned(), palette))
|
.map(|palette| Theme::custom("DevTools".to_owned(), palette))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mode = match &self.mode {
|
||||||
|
Mode::None => None,
|
||||||
|
Mode::Setup(setup) => {
|
||||||
|
let stage: Element<'_, _, Theme, P::Renderer> = match setup {
|
||||||
|
Setup::Idle => {
|
||||||
|
let controls = row![
|
||||||
|
button(text("Cancel").center().width(Fill))
|
||||||
|
.width(100)
|
||||||
|
.on_press(Message::CancelSetup)
|
||||||
|
.style(button::danger),
|
||||||
|
horizontal_space(),
|
||||||
|
button(text("Install").center().width(Fill))
|
||||||
|
.width(100)
|
||||||
|
.on_press(Message::InstallComet)
|
||||||
|
.style(button::success),
|
||||||
|
];
|
||||||
|
|
||||||
|
column![
|
||||||
|
text("comet is not installed!").size(20),
|
||||||
|
"In order to display performance metrics, the \
|
||||||
|
comet debugger must be installed in your system.",
|
||||||
|
"The comet debugger is an official companion tool \
|
||||||
|
that helps you debug your iced applications.",
|
||||||
|
"Do you wish to install it with the following \
|
||||||
|
command?",
|
||||||
|
container(
|
||||||
|
text(
|
||||||
|
"cargo install --locked \
|
||||||
|
--git https://github.com/iced-rs/comet.git"
|
||||||
|
)
|
||||||
|
.size(14)
|
||||||
|
)
|
||||||
|
.width(Fill)
|
||||||
|
.padding(5)
|
||||||
|
.style(container::dark),
|
||||||
|
controls,
|
||||||
|
]
|
||||||
|
.spacing(20)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
Setup::Running { logs } => column![
|
||||||
|
text("Installing comet...").size(20),
|
||||||
|
container(
|
||||||
|
scrollable(
|
||||||
|
column(
|
||||||
|
logs.iter()
|
||||||
|
.map(|log| text(log).size(12).into()),
|
||||||
|
)
|
||||||
|
.spacing(3),
|
||||||
|
)
|
||||||
|
.spacing(10)
|
||||||
|
.width(Fill)
|
||||||
|
.height(300)
|
||||||
|
.anchor_bottom(),
|
||||||
|
)
|
||||||
|
.padding(10)
|
||||||
|
.style(container::dark)
|
||||||
|
]
|
||||||
|
.spacing(20)
|
||||||
|
.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let setup = center(
|
||||||
|
container(stage)
|
||||||
|
.padding(20)
|
||||||
|
.width(500)
|
||||||
|
.style(container::bordered_box),
|
||||||
|
)
|
||||||
|
.padding(10)
|
||||||
|
.style(|_theme| {
|
||||||
|
container::Style::default()
|
||||||
|
.background(Color::BLACK.scale_alpha(0.8))
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(setup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map(|mode| {
|
||||||
|
themer(derive_theme(), Element::from(mode).map(Event::Message))
|
||||||
|
});
|
||||||
|
|
||||||
|
let notification = self.show_notification.then(|| {
|
||||||
|
themer(
|
||||||
|
derive_theme(),
|
||||||
bottom_right(
|
bottom_right(
|
||||||
container(text("Press F12 to open debug metrics"))
|
container(text("Press F12 to open debug metrics"))
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.style(container::dark),
|
.style(container::dark),
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
|
});
|
||||||
|
|
||||||
stack![view]
|
stack![view]
|
||||||
.push_maybe(self.show_notification.then_some(notification))
|
.push_maybe(mode)
|
||||||
|
.push_maybe(notification)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscription(&self, program: &P) -> Subscription<Message<P>> {
|
pub fn subscription(&self, program: &P) -> Subscription<Event<P>> {
|
||||||
let subscription =
|
let subscription =
|
||||||
program.subscription(&self.state).map(Message::Program);
|
program.subscription(&self.state).map(Event::Program);
|
||||||
|
|
||||||
let hotkeys =
|
let hotkeys =
|
||||||
futures::keyboard::on_key_press(|key, _modifiers| match key {
|
futures::keyboard::on_key_press(|key, _modifiers| match key {
|
||||||
|
|
@ -192,7 +376,8 @@ where
|
||||||
Some(Message::ToggleComet)
|
Some(Message::ToggleComet)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
})
|
||||||
|
.map(Event::Message);
|
||||||
|
|
||||||
Subscription::batch([subscription, hotkeys])
|
Subscription::batch([subscription, hotkeys])
|
||||||
}
|
}
|
||||||
|
|
@ -210,27 +395,22 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
enum Event<P>
|
||||||
enum Message<P>
|
|
||||||
where
|
where
|
||||||
P: Program,
|
P: Program,
|
||||||
{
|
{
|
||||||
HideNotification,
|
Message(Message),
|
||||||
ToggleComet,
|
|
||||||
Program(P::Message),
|
Program(P::Message),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> fmt::Debug for Message<P>
|
impl<P> fmt::Debug for Event<P>
|
||||||
where
|
where
|
||||||
P: Program,
|
P: Program,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Message::HideNotification => {
|
Self::Message(message) => message.fmt(f),
|
||||||
f.write_str("DevTools(HideNotification)")
|
Self::Program(message) => message.fmt(f),
|
||||||
}
|
|
||||||
Message::ToggleComet => f.write_str("DevTools(ToggleComet)"),
|
|
||||||
Message::Program(message) => message.fmt(f),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue