Rust Druid

#![windows_subsystem = "windows"]
mod cmd;
mod excel;
use druid::kurbo::{BezPath, Circle, Line};
use druid::piet::RenderContext;
use druid::widget::{
prelude::*, Button, Controller, CrossAxisAlignment, Either, EnvScope, Flex, Label,
MainAxisAlignment, Painter, Split, TextBox, ViewSwitcher,
};
use druid::{
theme, AppLauncher, Color, Data, FileDialogOptions, FileSpec, Lens, Menu, MenuItem, Point,
UnitPoint, WidgetExt, WindowDesc, WindowState,
};
use libloader::libloading;
use std::time::Instant;
use clipboard::ClipboardProvider;
use clipboard::ClipboardContext;
#[derive(Clone, Data, Lens)]
struct AppState {
display: String,
show_hide: bool,
current_view: u8,
sidebar_view: u8,
}
struct SwitchViews;
struct DragController;
struct MainMenuController;
struct MessageBoxTextController;
struct MessageBoxFocusController;
struct FolderIconColorController;
struct DocumentIconColorController;
impl Controller<AppState, Label<AppState>> for SwitchViews {
fn update(
&mut self,
child: &mut Label<AppState>,
ctx: &mut UpdateCtx,
old_data: &AppState,
data: &AppState,
env: &Env,
) {
if data.current_view == 0 {
child.set_text("NEXT🔜");
ctx.request_layout();
} else {
child.set_text("🔙PREVIOUS");
ctx.request_layout();
}
child.update(ctx, old_data, data, env);
}
}
impl<W: Widget<AppState>> Controller<AppState, W> for DragController {
fn event(
&mut self,
_child: &mut W,
ctx: &mut EventCtx,
event: &Event,
_data: &mut AppState,
_env: &Env,
) {
if let Event::MouseMove(_) = event {
ctx.window().handle_titlebar(true);
}
}
}
impl<W: Widget<AppState>> Controller<AppState, W> for MainMenuController {
fn event(
&mut self,
child: &mut W,
ctx: &mut druid::EventCtx,
event: &druid::Event,
data: &mut AppState,
env: &druid::Env,
) {
if let Event::MouseDown(e) = event {
if e.button.is_left() && ctx.is_hot() {
ctx.show_context_menu(make_main_menu(), Point::new(36., 28.));
}
}
child.event(ctx, event, data, env)
}
}
impl<W: Widget<AppState>> Controller<AppState, W> for MessageBoxTextController {
fn event(
&mut self,
child: &mut W,
ctx: &mut druid::EventCtx,
event: &druid::Event,
data: &mut AppState,
env: &druid::Env,
) {
if let Event::MouseDown(e) = event {
if e.button == druid::MouseButton::Left {
data.display = String::from("正在爬取沪深京A股数据请稍后->->->");
}
}
child.event(ctx, event, data, env)
}
}
impl<W: Widget<AppState>> Controller<AppState, W> for MessageBoxFocusController {
fn event(
&mut self,
child: &mut W,
ctx: &mut druid::EventCtx,
event: &druid::Event,
data: &mut AppState,
env: &druid::Env,
) {
if let Event::MouseDown(e) = event {
if e.button == druid::MouseButton::Left {
ctx.request_focus();
}
}
child.event(ctx, event, data, env)
}
}
impl Controller<AppState, Label<AppState>> for FolderIconColorController {
fn event(
&mut self,
child: &mut Label<AppState>,
ctx: &mut druid::EventCtx,
event: &druid::Event,
data: &mut AppState,
env: &druid::Env,
) {
if ctx.is_hot() || (data.show_hide == true && data.sidebar_view == 0) {
child.set_text_color(Color::WHITE);
ctx.request_layout();
} else {
child.set_text_color(Color::rgb8(170, 170, 170));
ctx.request_layout();
}
child.event(ctx, event, data, env)
}
fn update(
&mut self,
child: &mut Label<AppState>,
ctx: &mut UpdateCtx,
old_data: &AppState,
data: &AppState,
env: &Env,
) {
if data.show_hide == true && data.sidebar_view == 0 {
child.set_text_color(Color::WHITE);
ctx.request_layout();
} else {
child.set_text_color(Color::rgb8(170, 170, 170));
ctx.request_layout();
}
child.update(ctx, old_data, data, env);
}
}
impl Controller<AppState, Label<AppState>> for DocumentIconColorController {
fn event(
&mut self,
child: &mut Label<AppState>,
ctx: &mut druid::EventCtx,
event: &druid::Event,
data: &mut AppState,
env: &druid::Env,
) {
if ctx.is_hot() || (data.show_hide == true && data.sidebar_view == 1) {
child.set_text_color(Color::WHITE);
ctx.request_layout();
} else {
child.set_text_color(Color::rgb8(170, 170, 170));
ctx.request_layout();
}
child.event(ctx, event, data, env)
}
fn update(
&mut self,
child: &mut Label<AppState>,
ctx: &mut UpdateCtx,
old_data: &AppState,
data: &AppState,
env: &Env,
) {
if data.show_hide == true && data.sidebar_view == 1 {
child.set_text_color(Color::WHITE);
ctx.request_layout();
} else {
child.set_text_color(Color::rgb8(170, 170, 170));
ctx.request_layout();
}
child.update(ctx, old_data, data, env);
}
}
fn main() {
let initial_state: AppState = AppState {
display: "".to_string(),
show_hide: true,
current_view: 0,
sidebar_view: 0,
};
let main_window: WindowDesc<AppState> = WindowDesc::new(ui_builder())
.title("aimoss")
.show_titlebar(false);
AppLauncher::with_window(main_window)
.log_to_console()
.configure_env(|env: &mut Env, _| {
env.set(theme::WINDOW_BACKGROUND_COLOR, Color::rgb8(35, 35, 35));
})
.launch(initial_state)
.expect("launch failed");
}
fn ui_builder() -> impl Widget<AppState> {
let window: Split<AppState> = Split::columns(sidebar(), main_view())
.draggable(true)
.solid_bar(true)
.split_point(0.1)
.bar_size(0.0)
.min_bar_area(0.1);
let main_window: Either<AppState> = Either::new(
|data: &AppState, _env: &Env| data.show_hide,
window,
main_view(),
);
let menu: druid::widget::ControllerHost<
druid::widget::ControllerHost<
druid::widget::Container<AppState>,
druid::widget::Click<AppState>,
>,
MainMenuController,
> = Label::new("☰")
.with_text_size(17.)
.align_vertical(UnitPoint::new(0.5, 0.3))
.fix_size(30., 28.)
.background(icon_painter())
.on_click(|_, _, _| todo!())
.controller(MainMenuController {});
let titlebar: Flex<AppState> = Flex::column()
.with_child(
Flex::row()
.with_child(menu)
.with_flex_child(
Label::new("")
.center()
.controller(DragController {})
.expand_width(),
1.,
)
.with_child(
Label::new("一")
.with_text_size(13.)
.align_vertical(UnitPoint::new(0.5, 0.5))
.on_click(|ctx: &mut EventCtx, _data: &mut AppState, _env: &Env| {
ctx.window()
.clone()
.set_window_state(WindowState::Minimized)
})
.background(icon_painter())
.fix_size(30., 28.),
)
.with_child(
Label::new("⬜")
.center()
.on_click(|ctx: &mut EventCtx, _data: &mut AppState, _env: &Env| {
if ctx.window().get_window_state() == WindowState::Restored {
ctx.window()
.clone()
.set_window_state(WindowState::Maximized);
} else {
ctx.window().clone().set_window_state(WindowState::Restored);
}
})
.background(icon_painter())
.fix_size(33., 28.),
)
.with_child(
Label::new("⨉")
.center()
.on_click(|ctx: &mut EventCtx, _data: &mut AppState, _env: &Env| {
ctx.submit_command(druid::commands::QUIT_APP)
})
.background(close_painter())
.fix_size(35., 28.),
)
.fix_height(28.),
)
.with_flex_child(main_window, 1.0);
Flex::row()
.with_child(toolbar())
.with_flex_child(titlebar, 1.0)
}
fn close_painter() -> Painter<AppState> {
Painter::new(|ctx: &mut PaintCtx, _, _env: &Env| {
let bounds: druid::kurbo::RoundedRect = ctx.size().to_rect().to_rounded_rect(5.0);
if ctx.is_hot() {
ctx.fill(bounds, &Color::rgb8(255, 0, 0)); //红色
}
})
}
fn icon_painter() -> Painter<AppState> {
Painter::new(|ctx: &mut PaintCtx, _, _env: &Env| {
let bounds: druid::kurbo::RoundedRect = ctx.size().to_rect().to_rounded_rect(5.0);
if ctx.is_hot() {
ctx.fill(bounds, &Color::rgb8(70, 70, 70)); //亮灰色
}
})
}
fn toolbar() -> impl Widget<AppState> {
let appicon: druid::widget::ControllerHost<druid::widget::SizedBox<AppState>, DragController> =
Label::new("🐼")
.with_text_size(19.)
.with_text_color(Color::rgb8(153, 119, 255))
.with_text_color(Color::rgb8(17, 153, 221)) //vscode蓝/238, 221, 187VIP色
.center()
.fix_width(36.)
.controller(DragController {});
let folder: druid::widget::ControllerHost<
druid::widget::SizedBox<AppState>,
druid::widget::Click<AppState>,
> = Label::new("📁")
.with_text_size(17.)
.with_text_color(Color::rgb8(170, 170, 170)) //灰色
.controller(FolderIconColorController {})
.center()
.fix_width(36.)
.on_click(
move |_event: &mut EventCtx, data: &mut AppState, _env: &Env| {
if data.sidebar_view != 0 {
data.sidebar_view = 0;
data.show_hide = true;
} else {
data.show_hide = !data.show_hide
}
},
);
let document: druid::widget::ControllerHost<
druid::widget::SizedBox<AppState>,
druid::widget::Click<AppState>,
> = Label::new("📄")
.with_text_size(17.)
.with_text_color(Color::rgb8(170, 170, 170)) //灰色
.controller(DocumentIconColorController {})
.center()
.fix_width(36.)
.on_click(
move |_event: &mut EventCtx, data: &mut AppState, _env: &Env| {
if data.sidebar_view != 1 {
data.sidebar_view = 1;
data.show_hide = true;
} else {
data.show_hide = !data.show_hide
}
},
);
Flex::column()
.main_axis_alignment(MainAxisAlignment::Start)
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(appicon)
.with_default_spacer()
.with_child(folder)
.with_default_spacer()
.with_child(document)
.with_default_spacer()
.with_flex_child(
Label::new("")
.center()
.controller(DragController {})
.expand_height(),
1.,
)
.with_flex_spacer(0.1) //是否居中显示控件
.fix_width(36.0)
}
fn primary_sidebar_painter() -> Painter<AppState> {
Painter::new(|ctx: &mut PaintCtx, _, _env: &Env| {
let bounds: druid::kurbo::RoundedRect = ctx.size().to_rect().to_rounded_rect(5.);
ctx.fill(bounds, &Color::rgb8(50, 50, 50));
if ctx.is_hot() {
ctx.fill(bounds, &Color::rgb8(70, 70, 70)); //亮灰色
}
if ctx.is_active() {
ctx.fill(bounds, &Color::rgb8(25, 25, 25));
}
})
}
// fn custom_button_painter()-> Painter<AppState>{
// Painter::new(|ctx: &mut PaintCtx, _, _env: &Env| {
// let bounds: druid::kurbo::RoundedRect = ctx.size().to_rect().to_rounded_rect(5.);
// ctx.fill(bounds, &Color::rgb8(50, 50, 50));
// if ctx.is_hot() {
// ctx.stroke(bounds, &Color::WHITE, 1.0);
// }
// if ctx.is_active() {
// ctx.fill(bounds, &Color::rgb8(30, 30, 30));
// ctx.stroke(bounds, &Color::WHITE, 1.0);
// }
// })
// }
fn primary_sidebar() -> impl Widget<AppState> {
let switch: druid::widget::ControllerHost<
druid::widget::SizedBox<AppState>,
druid::widget::Click<AppState>,
> = Label::new("NEXT🔜")
.controller(SwitchViews {})
.center()
.background(primary_sidebar_painter())
.fix_width(800.)
.on_click(
move |_event: &mut EventCtx, data: &mut AppState, _env: &Env| {
if data.current_view == 0 {
data.current_view = 1;
} else {
data.current_view = 0;
}
},
);
let ncpa: druid::widget::ControllerHost<
druid::widget::SizedBox<AppState>,
druid::widget::Click<AppState>,
> = Label::new("网络连接")
.center()
.background(primary_sidebar_painter())
.fix_width(800.)
.on_click(|_ctx, _data, _env| {
cmd::ncpa();
});
let stock_data: druid::widget::ControllerHost<
druid::widget::ControllerHost<
druid::widget::SizedBox<AppState>,
druid::widget::Click<AppState>,
>,
MessageBoxTextController,
> = Label::new("沪深京A股")
.center()
.background(primary_sidebar_painter())
.fix_width(800.)
.on_click(|_ctx: &mut EventCtx, data: &mut AppState, _env: &Env| {
libloader::get_libfn!("reqwest.dll", "stock", stock_crawler, bool);
let now: Instant = Instant::now();
let rusult: bool = stock_crawler();
let end: u128 = now.elapsed().as_millis();
match rusult {
true => data.display = format!("沪深京A股数据爬取成功=>用时:{}毫秒", end),
false => data.display = format!("发生错误,请检查网络连接=>耗时:{}毫秒", end),
}
})
.controller(MessageBoxTextController {});
let control: druid::widget::ControllerHost<
druid::widget::SizedBox<AppState>,
druid::widget::Click<AppState>,
> = Label::new("控制面板")
.center()
.background(primary_sidebar_painter())
.fix_width(800.)
.on_click(|_ctx, _data, _env| {
cmd::control();
});
let new_excel: druid::widget::ControllerHost<
druid::widget::SizedBox<AppState>,
druid::widget::Click<AppState>,
> = Label::new("新建Excel表")
.center()
.background(primary_sidebar_painter())
.fix_width(800.)
.on_click(|_ctx, _data, _env| {
excel::new_excel();
});
let ssqiu_data: druid::widget::ControllerHost<druid::widget::SizedBox<AppState>, druid::widget::Click<AppState>> = Label::new("双色球")
.center()
.background(primary_sidebar_painter())
.fix_width(800.)
.on_click(|_ctx: &mut EventCtx, data: &mut AppState, _env: &Env| {
libloader::get_libfn!("reqwest.dll", "ssqiu", ssqiu_crawler, String);
let rusult: String = ssqiu_crawler();
data.display = rusult;
});
Flex::column()
.with_child(switch)
.with_default_spacer()
.with_child(ncpa)
.with_default_spacer()
.with_child(stock_data)
.with_default_spacer()
.with_child(control)
.with_default_spacer()
.with_child(new_excel)
.with_default_spacer()
.with_child(ssqiu_data)
.with_flex_spacer(1.)
.border(Color::WHITE, 0.1)
}
fn associate_sidebar() -> impl Widget<AppState> {
Flex::column()
.with_child(Label::new("副侧栏"))
.with_child(Label::new("1"))
.with_child(Label::new("2"))
.with_child(Label::new("3"))
.with_flex_spacer(1.)
.border(Color::WHITE, 0.1)
}
fn sidebar() -> impl Widget<AppState> {
ViewSwitcher::new(
|data: &AppState, _env: &Env| data.sidebar_view,
|selector: &u8, _data: &AppState, _env: &Env| match selector {
0 => Box::new(primary_sidebar()),
_ => Box::new(associate_sidebar()),
},
)
}
fn main_view() -> impl Widget<AppState> {
let clear_button: druid::widget::ControllerHost<
Button<AppState>,
druid::widget::Click<AppState>,
> = Button::new("清空").on_click(|_ctx: &mut EventCtx, data: &mut AppState, _env: &Env| {
data.display.clear();
});
let clip_button: druid::widget::ControllerHost<
Button<AppState>,
druid::widget::Click<AppState>,
> = Button::new("剪切").on_click(|_ctx: &mut EventCtx, data: &mut AppState, _env: &Env| {
let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
ctx.set_contents(data.display.to_owned()).unwrap();
data.display.clear();
});
let copy_button: druid::widget::ControllerHost<
Button<AppState>,
druid::widget::Click<AppState>,
> = Button::new("复制").on_click(|_ctx: &mut EventCtx, data: &mut AppState, _env: &Env| {
let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
ctx.set_contents(data.display.to_owned()).unwrap();
});
let paste_button: druid::widget::ControllerHost<
Button<AppState>,
druid::widget::Click<AppState>,
> = Button::new("粘贴").on_click(|_ctx: &mut EventCtx, data: &mut AppState, _env: &Env| {
let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
let get_contents: String = ctx.get_contents().unwrap_or(String::from("ERROR:粘贴内容非文本类型"));
match data.display.is_empty() {
true => data.display += &get_contents,
false => data.display += &(String::from("\n")+&get_contents),
}
});
let message_box: druid::widget::LensWrap<
AppState,
String,
app_state_derived_lenses::display,
druid::widget::SizedBox<String>,
> = TextBox::multiline()
.with_placeholder("文本显示框")
.expand()
.lens(AppState::display);
let lower_right_widgets: druid::widget::Align<AppState> = Flex::column()
.with_child(clear_button)
.with_default_spacer()
.with_child(clip_button)
.with_default_spacer()
.with_child(copy_button)
.with_default_spacer()
.with_child(paste_button)
.align_horizontal(UnitPoint::new(0.5, 0.5));
let lower_widgets: Flex<AppState> = Flex::row()
.with_flex_child(message_box, 1.)
.with_flex_child(lower_right_widgets, 0.1);
let lower_widgets: EnvScope<AppState, Flex<AppState>> = EnvScope::new(
|env: &mut Env, _data: &AppState| {
env.set(theme::TEXTBOX_BORDER_WIDTH, 0.1);
env.set(theme::BACKGROUND_LIGHT, Color::rgb8(25, 25, 25));
env.set(theme::SCROLLBAR_COLOR, Color::rgb8(120, 120, 120))
},
lower_widgets,
);
let current_view: ViewSwitcher<AppState, u8> = ViewSwitcher::new(
|data: &AppState, _env: &Env| data.current_view,
|selector: &u8, _data: &AppState, _env: &Env| match selector {
0 => Box::new(panda_painter()),
_ => Box::new(triangle_painter()),
},
);
Split::rows(current_view, lower_widgets)
.draggable(true)
.solid_bar(true)
.split_point(0.7)
.bar_size(0.0)
.min_bar_area(0.1)
.controller(MessageBoxFocusController {})
}
fn make_main_menu() -> Menu<AppState> {
let menu: Menu<AppState> = Menu::empty();
let mut menu_file: Menu<AppState> = Menu::new("File");
let txt: FileSpec = FileSpec::new("Text file", &["txt"]);
let save_dialog_options = FileDialogOptions::new()
.allowed_types(vec![txt])
.default_type(txt)
.name_label("Target")
.title("OpenFile")
.button_text("Open");
let menu_open_act: MenuItem<AppState> = MenuItem::new("Open").on_activate(
move |ctx: &mut druid::menu::MenuEventCtx, _data: &mut AppState, _env: &Env| {
ctx.submit_command(druid::commands::SHOW_OPEN_PANEL.with(save_dialog_options.clone()));
},
);
menu_file = menu_file.entry(menu_open_act);
menu.entry(menu_file)
.entry(druid::platform_menus::win::file::save_as())
.entry(druid::platform_menus::win::file::open())
.entry(druid::platform_menus::win::file::new())
.entry(druid::platform_menus::win::file::exit())
}
fn panda_painter() -> Painter<AppState> {
Painter::new(|ctx: &mut PaintCtx, _, _env| {
// Draw panda's head
let head: Circle = Circle::new((200.0, 200.0), 100.0);
ctx.fill(head, &Color::WHITE);
ctx.stroke(head, &Color::BLACK, 2.0);
// Draw panda's left ear
let left_ear: Circle = Circle::new((130.0, 130.0), 30.0);
ctx.fill(left_ear, &Color::BLACK);
// Draw panda's right ear
let right_ear: Circle = Circle::new((270.0, 130.0), 30.0);
ctx.fill(right_ear, &Color::BLACK);
// Draw panda's left eye patch
let left_eye_patch: Circle = Circle::new((160.0, 200.0), 20.0);
ctx.fill(left_eye_patch, &Color::BLACK);
// Draw panda's right eye patch
let right_eye_patch: Circle = Circle::new((240.0, 200.0), 20.0);
ctx.fill(right_eye_patch, &Color::BLACK);
// Draw panda's left eye
let left_eye: Circle = Circle::new((160.0, 200.0), 10.0);
ctx.fill(left_eye, &Color::WHITE);
// Draw panda's right eye
let right_eye: Circle = Circle::new((240.0, 200.0), 10.0);
ctx.fill(right_eye, &Color::WHITE);
// Draw panda's nose
let nose: Circle = Circle::new((200.0, 230.0), 10.0);
ctx.fill(nose, &Color::BLACK);
// Draw panda's mouth
let mut mouth_path: BezPath = BezPath::new();
mouth_path.move_to((190.0, 250.0));
mouth_path.quad_to((200.0, 260.0), (210.0, 250.0));
ctx.stroke(mouth_path, &Color::BLACK, 2.0);
})
}
fn triangle_painter() -> Painter<AppState> {
Painter::new(|ctx: &mut PaintCtx, _, _| {
// let mut triangle_path: BezPath = BezPath::new();
// triangle_path.move_to(Point::new(100.0, 100.0));
// triangle_path.line_to(Point::new(200.0, 100.0));
// triangle_path.line_to(Point::new(300.0, 200.0));
// //triangle_path.line_to(Point::new(100.0, 200.0));
// triangle_path.close_path(); // Closes the path to the start point
// ctx.fill(triangle_path.clone(), &Color::rgb8(0, 0, 255));// Fill the triangle with blue color
// ctx.stroke(triangle_path, &Color::BLACK, 2.0);// Stroke the triangle with black color
let horizontal_line: Line = Line::new((100., 100.), (200., 100.));
let s: Line = Line::new((200., 100.), (200., 300.));
let h: Line = Line::new((200., 300.), (300., 300.));
let horizontal_linea: Line = Line::new((100., 200.), (100., 300.));
let sa: Line = Line::new((100., 200.), (300., 200.));
let ha: Line = Line::new((300., 100.), (300., 200.));
ctx.stroke(horizontal_line, &Color::RED, 2.0);
ctx.stroke(s, &Color::RED, 2.0);
ctx.stroke(h, &Color::RED, 2.0);
ctx.stroke(horizontal_linea, &Color::RED, 2.0);
ctx.stroke(sa, &Color::RED, 2.0);
ctx.stroke(ha, &Color::RED, 2.0);
})
}

浙公网安备 33010602011771号