Skip to content

Commit

Permalink
add k line; custom font; .etc
Browse files Browse the repository at this point in the history
  • Loading branch information
L1nY4n committed Nov 2, 2024
1 parent d6b4b62 commit 44f8ada
Show file tree
Hide file tree
Showing 13 changed files with 4,653 additions and 1,679 deletions.
4,999 changes: 3,702 additions & 1,297 deletions Cargo.lock

Large diffs are not rendered by default.

39 changes: 26 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
[package]
name = "stock-tracker"
version = "0.1.3"
version = "0.1.4"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
eframe = {version = "0.19.0", features = ["persistence"]}
tracing = "0.1.36"
tracing-subscriber = "0.3.15"
crossbeam="*"
lazy_static = "1.4.0"
eframe = {version = "0.29.1", features = ["persistence","__screenshot"]}

tracing = "0.1.40"
tracing-subscriber = "0.3.18"
crossbeam="0.8.4"
lazy_static = "1.5.0"
serde = { version = "1.0.136", features = ["derive"] }
url = "2.2.2"
ureq = "2.5.0"
encoding_rs = "0.8.31"
chrono = "0.4.22"
ehttp = "0.2.0"
egui_extras = "0.19.0"
regex = "1.6.0"
url = "2.5.2"
ureq = "2.10.1"
encoding_rs = "0.8.35"
chrono ={version = "0.4.38",features = ["serde"]}
ehttp = "0.5.0"
egui_extras = "0.29.1"
regex = "1.11.1"
egui_plot = "0.29.0"
once_cell = "1.20.2"
reqwest = { version = "0.12", features = ["json","blocking"] }



[profile.release]
codegen-units = 1
lto = "fat"
opt-level = "s"
panic = "abort"
strip = "debuginfo"
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# stock-tracker

A simple stock desktop app
A simple stock desktop app
gui by [egui](https://github.com/emilk/egui)


<div align="center">

<img src="./screenshots/screenshot1.png" alt="Sublime's custom image" width="300"/>

<img src="./screenshots/v0.1.2.png" alt="Sublime's custom image" width="300"/>
</div>
80 changes: 80 additions & 0 deletions main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // windows_subsystem 告诉编译器,程序运行时隐藏命令行窗口。
use eframe::egui;
use native_dialog::{FileDialog, MessageDialog, MessageType};
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(320.0, 340.0)), //初始化窗体size
..Default::default()
};
eframe::run_native(
"Hello word", //应用程序名称
options,
Box::new(|_cc| Box::<MyApp>::new(MyApp::new(_cc))), //第三个参数为程序构建器(eframe::AppCreator类型)负责创建应用程序上下文(egui::Context)。_cc为&CreationContextl类型,_cc.egui_ctx字段即为Context。
//之所以强调Context的创建过程,是因为显示中文字体需要配置Context。
)
}

struct MyApp {
name: String,
age: u32,
}
impl Default for MyApp {
fn default() -> Self {
Self {
name: "Arthur".to_owned(),
age: 42,
}
}
}
impl MyApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
load_font(&cc.egui_ctx); //egui默认字体无法显示中文,需要加载中文字体。配置字体应该在构造函数中。网上部分教程将字体配置写入了update函数,update函数每一帧都会运行一次,每秒60次,因此在update函数中加载字体是错误且低效的。
Self::default()
}
}

impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Egui 0.23 Application");
ui.horizontal(|ui| {
let name_label = ui.label("Your name: ");
ui.text_edit_multiline(&mut self.name)
.labelled_by(name_label.id);
});
ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
if ui.button("Open file").clicked() {
let path = FileDialog::new()
.set_location("~/Desktop")
.add_filter("PNG Image", &["png"])
.add_filter("JPEG Image", &["jpg", "jpeg"])
.show_open_single_file()
.unwrap();
if let Some(path) =path{
self.name=path.as_path().display().to_string();
}
}
ui.label(format!("Hello '{}', age {}", self.name, self.age));
});
}
}

// 为了支持中文,我们加载阿里巴巴普惠体字体:下载自https://fonts.alibabagroup.com/#/home
//将字体文件放置在src目录同级别的resources目录下
pub fn load_font(ctx: &egui::Context) {
let mut fonts = eframe::egui::FontDefinitions::default();
fonts.font_data.insert(
"AlibabaPuHuiTi-3-55-Regular".to_owned(),
eframe::egui::FontData::from_static(include_bytes!(
"../resources/AlibabaPuHuiTi-3-55-Regular.ttf"
)),
); // .ttf and .otf supported

fonts
.families
.get_mut(&eframe::egui::FontFamily::Proportional)
.unwrap()
.insert(0, "AlibabaPuHuiTi-3-55-Regular".to_owned());
ctx.set_fonts(fonts);
}
Binary file added resources/AlibabaPuHuiTi-3-55-Regular.ttf
Binary file not shown.
File renamed without changes
Binary file added screenshots/v0.1.4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions src/back/message.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
use super::stork_api::Stock;
use super::stock::{BaseData, KLineScale, KlineItem, Stock};

#[derive(Debug)]
pub enum ToBackend {
Refresh,
SetInterval(u32),
StockAdd(String),
StockDel(String),
StockKLine(String, KLineScale)
}

#[derive(Debug)]
pub enum ToFrontend {
DataList(Vec<Stock>),
DataList(Vec<(String,String,BaseData)>),
Data(String,String,BaseData),
Kline(String,Vec<KlineItem>)
}
126 changes: 100 additions & 26 deletions src/back/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ use crossbeam::{
channel::{tick, Receiver, Sender},
select,
};
use tracing::{error, info};
use eframe::egui::ahash::HashMap;
use stock::{KLineScale, Stock};
use tracing::error;

pub mod message;
use message::{ToBackend, ToFrontend};

use self::stork_api::check_stock_code;
use self::stock::check_stock_code;

pub mod stork_api;
pub mod stock;

#[derive(Debug, Clone)]
pub struct Back {
stock_codes: Vec<String>,
kline_scale_map: HashMap<String, KLineScale>,
back_tx: Sender<ToFrontend>,
front_rx: Receiver<ToBackend>,
}
Expand All @@ -30,64 +34,134 @@ impl Back {
back_tx,
front_rx,
stock_codes,
kline_scale_map: HashMap::default(),
}
}

pub fn init(&mut self) {
self.refetch(false);
#[warn(unused_assignments)]
let mut ticker = tick(Duration::from_secs(5));
pub fn run(&mut self) {
self.refetch_data();
self.refresh_kline();
let mut ticker = tick(Duration::from_millis(200));
let kline_ticker = tick(Duration::from_secs(60));
loop {
select! {
recv(self.front_rx)->msg =>{
match msg {
Ok(m)=>{
match m {
ToBackend::Refresh=>{
self.refetch(false);
self.refetch_data();
},
ToBackend::SetInterval(interval) => {
ticker = tick(Duration::from_secs(interval.into()));
ticker = tick(Duration::from_millis(interval.into()));
},
ToBackend::StockAdd(code) => {
// if self.stock_codes.contains(&code){
self.stock_codes.push(code);
// }

println!("add code {}",code);
self.add_stock(code);
},
ToBackend::StockDel(code) => {
self.stock_codes.retain(|x| x != &code);
self.refetch(true);
}
}
self.refetch_data();

},
ToBackend::StockKLine(code, scale) => {
let scale_int = scale.to_usize();
self.kline_scale_map.insert(code.clone(), scale);
match Stock::get_kelines(&code, &scale_int, 100) {
Ok(l) => {

let dl = ToFrontend::Kline(code.clone(), l);
self.back_tx.send(dl).ok();
},
Err(err) =>{
println!("kline error {}",err);
},
}

}
}}
Err(e) => {
error!("receive ToBackend msg faild : {}",e)
}
}
},
recv(kline_ticker)->_msg =>{

let this = self.clone();
std::thread::spawn(move || {
this.refresh_kline();
});

},
recv(ticker)->_msg =>{
self.refetch(false);
self.refetch_data();
},
}
}
}

fn refetch(&self, focus: bool) {
if !self.stock_codes.is_empty() {
match stork_api::fetch_blocking(self.stock_codes.clone()) {
Ok(stocks) => {
let dl = ToFrontend::DataList(stocks);
self.back_tx.send(dl).ok();
fn add_stock(&mut self, code: String) {
if !self.stock_codes.contains(&code) {
match stock::fetch_blocking(vec![code.to_string()]) {
Ok(datas) => {
datas.iter().for_each(|(code, name, data)| {
let dl = ToFrontend::Data(code.clone(), name.clone(), data.clone());
self.back_tx.send(dl).ok();
self.stock_codes.push(code.clone());

let scale = self
.kline_scale_map
.get(code)
.unwrap_or(&KLineScale::Munute15);
match Stock::get_kelines(code, &scale.to_usize(), 100) {
Ok(kl) => {
self.back_tx.send(ToFrontend::Kline(code.clone(), kl)).ok();
}
Err(err) => {
println!("get kline error {}", err);
}
}
});
}
Err(e) => {
error!(e)
error!("add stock error {}", e)
}
}
} else {
if focus {
self.back_tx.send(ToFrontend::DataList(vec![])).ok();
println!("stock {} already exists", code);
}
}

fn refetch_data(&self) {
if !self.stock_codes.is_empty() {
match stock::fetch_blocking(self.stock_codes.clone()) {
Ok(datas) => {
let dl = ToFrontend::DataList(datas);
self.back_tx.send(dl).ok();
}
Err(e) => {
error!("fetch data error {}", e)
}
}
}
}

fn refresh_kline(&self) {
if !self.stock_codes.is_empty() {
self.stock_codes.iter().for_each(|code| {
let scale = self
.kline_scale_map
.get(code)
.unwrap_or(&KLineScale::Munute15);
match Stock::get_kelines(code, &scale.to_usize(), 100) {
Ok(kl) => {
self.back_tx.send(ToFrontend::Kline(code.clone(), kl)).ok();
}
Err(err) => {
println!("get kline error {}", err);
}
}
});
}
}
}
Loading

0 comments on commit 44f8ada

Please sign in to comment.