在Rust的 Iced框架中,如何使用线程来改变标签的颜色?
我想用Rust(并使用iced 0.12)来实现一个图形界面。它有一个标签,颜色有三种可能:绿色、橙色、红色。颜色应每两秒循环变化(绿色、橙色、红色、绿色、橙色、红色……)。下面是代码。在调试时,我看到线程确实在改变颜色,但图形界面只显示红色。这里缺少了什么?
use iced::widget::{container, text};
use iced::{Application, Command, Element, Settings, Theme, Color, Subscription};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
#[derive(Debug, Clone)]
pub enum Message {
ColorChanged(ColorState),
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum ColorState {
Red,
Orange,
Green,
}
impl ColorState {
fn next(self) -> Self {
match self {
ColorState::Red => ColorState::Orange,
ColorState::Orange => ColorState::Green,
ColorState::Green => ColorState::Red,
}
}
fn to_color(self) -> Color {
match self {
ColorState::Red => Color::from_rgb(0.8, 0.0, 0.0),
ColorState::Orange => Color::from_rgb(1.0, 0.5, 0.0),
ColorState::Green => Color::from_rgb(0.0, 0.8, 0.0),
}
}
fn to_text(self) -> &'static str {
match self {
ColorState::Red => "Red",
ColorState::Orange => "Orange",
ColorState::Green => "Green",
}
}
}
struct ColorCycleApp {
current_color: ColorState,
color_state: Arc<Mutex<ColorState>>,
}
impl Application for ColorCycleApp {
type Message = Message;
type Theme = Theme;
type Executor = iced::executor::Default;
type Flags = ();
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
let color_state = Arc::new(Mutex::new(ColorState::Red));
// Start the background thread
let color_state_clone = Arc::clone(&color_state);
thread::spawn(move || {
loop {
thread::sleep(Duration::from_secs(2));
let mut state = color_state_clone.lock().unwrap();
*state = state.next();
println!("Thread changed color to: {:?}", *state);
}
});
(
Self {
current_color: ColorState::Red,
color_state,
},
Command::none(),
)
}
fn title(&self) -> String {
String::from("My GUI")
}
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match message {
Message::ColorChanged(new_color) => {
self.current_color = new_color;
println!("New color: {:?}", new_color);
Command::none()
}
}
}
fn view(&self) -> Element<Self::Message> {
let current_color = self.current_color.to_color();
let label = text(self.current_color.to_text())
.size(48);
let colored_container = container(label)
.width(iced::Length::Fill)
.height(iced::Length::Fill)
.center_x()
.center_y()
.style(move |_theme: &Theme| container::Appearance {
background: Some(iced::Background::Color(current_color)),
..Default::default()
});
colored_container.into()
}
fn subscription(&self) -> Subscription<Self::Message> {
let color_state = Arc::clone(&self.color_state);
iced::subscription::unfold(
"color_monitor",
ColorState::Red,
move |last_color| {
let color_state = Arc::clone(&color_state);
async move {
loop {
tokio::time::sleep(Duration::from_millis(100)).await;
let current_color = {
let state = color_state.lock().unwrap();
*state
};
if current_color != last_color {
return (Message::ColorChanged(current_color), current_color);
}
}
}
}
)
}
}
fn main() -> iced::Result {
ColorCycleApp::run(Settings {
window: iced::window::Settings {
size: iced::Size::new(400.0, 300.0),
..Default::default()
},
..Default::default()
})
}
解决方案
问题就是这点:
thread '<unnamed>' (23476723) panicked at src/main.rs:123:25:
there is no reactor running, must be called from the context of a Tokio 1.x runtime
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
这由 tokio::time::sleep(Duration::from_millis(100)).await; 引起。
我并不完全理解你在这里为什么要使用 tokio,但如果你确实需要,你需要一个 reactor,如错误信息所述。然而这并不容易通过 iced 实现,因为它的 async 执行器基于 futures 和 async-std 风格的原语,并且并非建立在 tokio 之上。
最简单的修复是直接使用 std::thread::sleep 来替代。
这是应用了修正(以及一些其他小改动)的完整代码:
use iced::widget::{container, text};
use iced::{Application, Color, Command, Element, Settings, Subscription, Theme};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
#[derive(Debug, Clone)]
pub(crate) enum Message {
ColorChanged(ColorState),
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum ColorState {
Red,
Orange,
Green,
}
impl ColorState {
fn next(self) -> Self {
match self {
ColorState::Red => ColorState::Orange,
ColorState::Orange => ColorState::Green,
ColorState::Green => ColorState::Red,
}
}
fn to_color(self) -> Color {
match self {
ColorState::Red => Color::from_rgb(0.8, 0.0, 0.0),
ColorState::Orange => Color::from_rgb(1.0, 0.5, 0.0),
ColorState::Green => Color::from_rgb(0.0, 0.8, 0.0),
}
}
fn to_text(self) -> &'static str {
match self {
ColorState::Red => "Red",
ColorState::Orange => "Orange",
ColorState::Green => "Green",
}
}
}
struct ColorCycleApp {
current_color: ColorState,
color_state: Arc<Mutex<ColorState>>,
}
impl Application for ColorCycleApp {
type Message = Message;
type Theme = Theme;
type Executor = iced::executor::Default;
type Flags = ();
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
let color_state = Arc::new(Mutex::new(ColorState::Red));
// Start the background thread
let color_state_clone = Arc::clone(&color_state);
thread::spawn(move || {
loop {
thread::sleep(Duration::from_secs(2));
let mut state = color_state_clone.lock().unwrap();
*state = state.next();
println!("Thread changed color to: {:?}", *state);
}
});
(
Self {
current_color: ColorState::Red,
color_state,
},
Command::none(),
)
}
fn title(&self) -> String {
String::from("My GUI")
}
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match message {
Message::ColorChanged(new_color) => {
self.current_color = new_color;
println!("New color: {:?}", new_color);
Command::none()
}
}
}
fn view(&'_ self) -> Element<'_, Self::Message> {
let current_color = self.current_color.to_color();
let label = text(self.current_color.to_text()).size(48);
let colored_container = container(label)
.width(iced::Length::Fill)
.height(iced::Length::Fill)
.center_x()
.center_y()
.style(move |_theme: &Theme| container::Appearance {
background: Some(iced::Background::Color(current_color)),
..Default::default()
});
colored_container.into()
}
fn subscription(&self) -> Subscription<Self::Message> {
let color_state = Arc::clone(&self.color_state);
iced::subscription::unfold("color_monitor", ColorState::Red, move |last_color| {
let color_state = Arc::clone(&color_state);
async move {
loop {
std::thread::sleep(Duration::from_millis(100));
let current_color = {
let state = color_state.lock().unwrap();
*state
};
if current_color != last_color {
return (Message::ColorChanged(current_color), current_color);
}
}
}
})
}
}
fn main() -> iced::Result {
ColorCycleApp::run(Settings {
window: iced::window::Settings {
size: iced::Size::new(400.0, 300.0),
..Default::default()
},
..Default::default()
})
}
替代方案
如果你确实希望保留 async-await 风格的sleep机制,你可以改用 futures-timer crate,并依赖类似下面这样的实现:
futures_timer::Delay::new(Duration::from_millis(100)).await;
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。