message_stream/
message_stream.rs1#![allow(deprecated)]
4
5use gtk::prelude::*;
6use relm4::{Sender, prelude::*};
7
8struct Dialog {
9 buffer: gtk::EntryBuffer,
10}
11
12#[derive(Debug)]
13enum DialogMsg {
14 Accept,
15 Cancel,
16}
17
18#[relm4::component]
19impl SimpleComponent for Dialog {
20 type Init = ();
21 type Input = DialogMsg;
22 type Output = String;
23 type Widgets = DialogWidgets;
24
25 view! {
26 #[root]
27 dialog = gtk::MessageDialog {
28 set_margin_all: 12,
29 set_modal: true,
30 set_text: Some("Enter a search query"),
31 add_button: ("Search", gtk::ResponseType::Accept),
32 present: (),
33
34 connect_response[sender] => move |dialog, resp| {
35 dialog.set_visible(false);
36 sender.input(if resp == gtk::ResponseType::Accept {
37 DialogMsg::Accept
38 } else {
39 DialogMsg::Cancel
40 });
41 }
42 },
43 dialog.content_area() -> gtk::Box {
44 gtk::Entry {
45 set_buffer: &model.buffer,
46 }
47 }
48 }
49
50 fn init(
51 _: Self::Init,
52 root: Self::Root,
53 sender: ComponentSender<Self>,
54 ) -> ComponentParts<Self> {
55 let model = Dialog {
56 buffer: gtk::EntryBuffer::default(),
57 };
58 let widgets = view_output!();
59
60 ComponentParts { model, widgets }
61 }
62
63 fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
64 match msg {
65 DialogMsg::Accept => {
66 sender.output(self.buffer.text().into()).unwrap();
67 }
68 DialogMsg::Cancel => {
69 sender.output(String::default()).unwrap();
70 }
71 }
72 }
73
74 fn shutdown(&mut self, _widgets: &mut Self::Widgets, _output: Sender<Self::Output>) {
75 println!("Dialog shutdown");
76 }
77}
78
79#[derive(Debug)]
80enum AppMsg {
81 StartSearch,
82}
83
84struct App {
85 result: Option<String>,
86 searching: bool,
87}
88
89#[relm4::component]
90impl Component for App {
91 type Init = ();
92 type Input = AppMsg;
93 type Output = ();
94 type Widgets = AppWidgets;
95 type CommandOutput = Option<String>;
96
97 view! {
98 main_window = gtk::ApplicationWindow {
99 set_default_size: (300, 100),
100
101 gtk::Box {
102 set_orientation: gtk::Orientation::Vertical,
103 set_margin_all: 12,
104 set_spacing: 12,
105
106 if let Some(result) = &model.result {
107 gtk::LinkButton {
108 set_label: "Your search result",
109 #[watch]
110 set_uri: result,
111 }
112 } else {
113 gtk::Label {
114 set_label: "Click the button to start a web-search"
115 }
116 },
117 gtk::Button {
118 set_label: "Start search",
119 connect_clicked => AppMsg::StartSearch,
120 #[watch]
121 set_sensitive: !model.searching,
122 }
123 }
124 }
125 }
126
127 fn init(
128 _: Self::Init,
129 root: Self::Root,
130 _sender: ComponentSender<Self>,
131 ) -> ComponentParts<Self> {
132 let model = App {
133 result: None,
134 searching: false,
135 };
136 let widgets = view_output!();
137
138 ComponentParts { model, widgets }
139 }
140
141 fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>, root: &Self::Root) {
142 match msg {
143 AppMsg::StartSearch => {
144 self.searching = true;
145
146 let stream = Dialog::builder()
147 .transient_for(root)
148 .launch(())
149 .into_stream();
150 sender.oneshot_command(async move {
151 let result = stream.recv_one().await;
153
154 if let Some(search) = result {
155 let response =
156 reqwest::get(format!("https://duckduckgo.com/lite/?q={search}"))
157 .await
158 .unwrap();
159 let response_text = response.text().await.unwrap();
160
161 if let Some(url) = response_text.split("<a rel=\"nofollow\" href=\"").nth(1)
163 {
164 let url = url.split('\"').next().unwrap().replace("amp;", "");
165 Some(format!("https:{url}"))
166 } else {
167 None
168 }
169 } else {
170 None
171 }
172 });
173 }
174 }
175 }
176
177 fn update_cmd(
178 &mut self,
179 message: Self::CommandOutput,
180 _sender: ComponentSender<Self>,
181 _root: &Self::Root,
182 ) {
183 self.searching = false;
184 self.result = message;
185 }
186}
187
188fn main() {
189 let app = RelmApp::new("relm4.example.message_stream");
190 app.run::<App>(());
191}