学习Rust编程rs构建一个新闻类桌面应用程序

[dependencies]
gtk = { version = "0.3.0" ,features=["v3_18"]}
reqwest = {version="0.9"}
serde_json = "1.0.96"
serde_derive = "1.0.163"
serde = "1.0.163"
crossbeam = "0.8"
// hews/src/hackernews.rs

use crate::hackernews::top_stories;
use crate::hackernews::Story;
use gtk::*;
use reqwest::Client;
use std::process;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;

pub enum Msg {
    NewStory(Story),
    Loading,
    Loaded,
    Refresh,
}

#[derive(Clone)]
pub struct Header {
    pub header: HeaderBar,
    pub refresh_btn: Button,
}

impl Header {
    pub fn new(story_container: gtk::Box, tx: Sender) -> Header {
        let header = HeaderBar::new();
        let refresh_btn = gtk::Button::new_with_label("Refresh");
        refresh_btn.set_sensitive(false);
        header.pack_start(&refresh_btn);
        header.set_title("Hews - popular stories from hacker news");
        header.set_show_close_button(true);

        refresh_btn.connect_clicked(move |_| {
            for i in story_container.get_children().iter().skip(1) {
                story_container.remove(i);
            }
            tx.send(Msg::Refresh).unwrap();
        });

        Header {
            header,
            refresh_btn,
        }
    }

    fn disable_refresh(&self) {
        self.refresh_btn.set_label("Loading");
        self.refresh_btn.set_sensitive(false);
    }

    fn enable_refresh(&self) {
        self.refresh_btn.set_label("Refresh");
        self.refresh_btn.set_sensitive(true);
    }
}

pub struct App {
    window: Window,
    header: Header,
    stories: gtk::Box,
    spinner: Spinner,
    tx: Sender,
}

impl App {
    pub fn new() -> (App, Receiver) {
        if gtk::init().is_err() {
            println!("Failed to init hews window");
            process::exit(1);
        }

        let (tx, rx) = channel();
        let window = gtk::Window::new(gtk::WindowType::Toplevel);
        let sw = ScrolledWindow::new(None, None);
        let stories = gtk::Box::new(gtk::Orientation::Vertical, 20);
        let spinner = gtk::Spinner::new();
        let header = Header::new(stories.clone(), tx.clone());

        stories.pack_start(&spinner, false, false, 2);
        sw.add(&stories);
        window.add(&sw);
        window.set_default_size(600, 350);
        window.set_titlebar(&header.header);

        window.connect_delete_event(move |_, _| {
            main_quit();
            Inhibit(false)
        });

        (
            App {
                window,
                header,
                stories,
                spinner,
                tx,
            },
            rx,
        )
    }

    pub fn launch(&self, rx: Receiver) {
        self.window.show_all();
        let client = Arc::new(reqwest::Client::new());
        self.fetch_posts(client.clone());
        self.run_event_loop(rx, client);
    }

    fn fetch_posts(&self, client: Arc) {
        self.spinner.start();
        self.tx.send(Msg::Loading).unwrap();
        let tx_clone = self.tx.clone();
        top_stories(client, 10, &tx_clone);
    }

    fn render_story(s: Story, stories: >k::Box) {
        let title_with_score = format!("{} ({})", s.title, s.score);
        let label = gtk::Label::new(&*title_with_score);
        let story_url = s.url.unwrap_or("N/A".to_string());
        let link_label = gtk::Label::new(&*story_url);
        let label_markup = format!("{}", story_url, story_url);
        link_label.set_markup(&label_markup);
        stories.pack_start(&label, false, false, 2);
        stories.pack_start(&link_label, false, false, 2);
        stories.show_all();
    }

    fn run_event_loop(&self, rx: Receiver, client: Arc) {
        let container = self.stories.clone();
        let spinner = self.spinner.clone();
        let header = self.header.clone();
        let tx_clone = self.tx.clone();

        gtk::timeout_add(100, move || {
            match rx.try_recv() {
                Ok(Msg::NewStory(s)) => App::render_story(s, &container),
                Ok(Msg::Loading) => header.disable_refresh(),
                Ok(Msg::Loaded) => {
                    spinner.stop();
                    header.enable_refresh();
                }
                Ok(Msg::Refresh) => {
                    spinner.start();
                    spinner.show();
                    (&tx_clone).send(Msg::Loading).unwrap();
                    top_stories(client.clone(), 10, &tx_clone);
                }
                Err(_) => {}
            }
            gtk::Continue(true)
        });

        gtk::main();
    }
}
// hews/src/hackernews.rs

use crate::app::Msg;
use reqwest::Client;
use serde_derive::Deserialize;
use serde_json::Value;
use std::sync::mpsc::Sender;
use std::sync::Arc;
use std::thread;

const HN_BASE_URL: &str = "https://hacker-news.firebaseio.com/v0/";

#[derive(Deserialize, Debug)]
pub struct Story {
    pub by: String,
    pub id: u32,
    pub score: u64,
    pub time: u64,
    pub title: String,
    #[serde(rename = "type")]
    pub _type: String,
    pub url: Option,
    pub kids: Option,
    pub descendents: Option,
}

fn fetch_stories_parsed(client: &Client) -> Result {
    let stories_url = format!("{}topstories.json", HN_BASE_URL);
    let body = client.get(&stories_url).send()?.text()?;
    let story_ids: Value = serde_json::from_str(&body).unwrap();
    Ok(story_ids)
}

pub fn top_stories(client: Arc, count: usize, tx: &Sender) {
    let tx_clone = tx.clone();
    thread::spawn(move || {
        let story_ids = fetch_stories_parsed(&client).unwrap();
        let filtered: Vec<&Value> = story_ids.as_array()
                                             .unwrap()
                                             .iter()
                                             .take(count)
                                             .collect();

        let loaded = !filtered.is_empty();

        for id in filtered {
            let id = id.as_u64().unwrap();
            let story_url = format!("{}item/{}.json", HN_BASE_URL, id);
            let story = client.get(&story_url)
                              .send()
                              .unwrap()
                              .text()
                              .unwrap();
            let story: Story = serde_json::from_str(&story).unwrap();
            tx_clone.send(Msg::NewStory(story)).unwrap();
        }

        if loaded {
            tx_clone.send(Msg::Loaded).unwrap();
        }
    });
}
// hews/src/main.rs

mod app;
mod hackernews;

use crate::app::App;

fn main() {
    let (app, rx) = App::new();
    app.launch(rx);
}
展开阅读全文

页面更新:2024-04-14

标签:应用程序   桌面   新闻

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top