浏览代码

Add option to store and restore last session

Johannes Hofmann 7 年前
父节点
当前提交
bfa801d2e3
共有 4 个文件被更改,包括 193 次插入23 次删除
  1. 61
    23
      src/config.rs
  2. 26
    0
      src/main.rs
  3. 15
    0
      src/map_view_gl.rs
  4. 91
    0
      src/session.rs

+ 61
- 23
src/config.rs 查看文件

@@ -1,5 +1,6 @@
1 1
 use clap;
2 2
 use directories::ProjectDirs;
3
+use session::Session;
3 4
 use std::fmt::Debug;
4 5
 use std::fs::File;
5 6
 use std::io::{Read, Write};
@@ -24,6 +25,7 @@ pub struct Config {
24 25
     fps: f64,
25 26
     use_network: bool,
26 27
     async: bool,
28
+    open_last_session: bool,
27 29
 }
28 30
 
29 31
 impl Config {
@@ -72,27 +74,6 @@ impl Config {
72 74
         }
73 75
     }
74 76
 
75
-    fn create_config_file<P: AsRef<Path> + Debug>(dir_path: P, file_path: P, contents: &[u8]) -> Result<(), String> {
76
-        if !dir_path.as_ref().is_dir() {
77
-            if let Err(err) = ::std::fs::create_dir_all(&dir_path) {
78
-                return Err(format!("failed to create config directory ({:?}): {}",
79
-                    dir_path,
80
-                    err
81
-                ));
82
-            }
83
-        }
84
-
85
-        let mut file = File::create(&file_path)
86
-            .map_err(|err| format!("failed to create config file {:?}: {}", &file_path, err))?;
87
-
88
-        file.write_all(contents)
89
-            .map_err(|err| format!(
90
-                "failed to write contents to config file {:?}: {}",
91
-                &file_path,
92
-                err
93
-            ))
94
-    }
95
-
96 77
     fn find_or_create() -> Result<Config, String> {
97 78
         let config_dir = PROJ_DIRS.config_dir();
98 79
         let config_file = {
@@ -108,7 +89,7 @@ impl Config {
108 89
         } else {
109 90
             // try to write a default config file
110 91
 
111
-            match Config::create_config_file(
92
+            match create_config_file(
112 93
                 config_dir,
113 94
                 &config_file,
114 95
                 DEFAULT_CONFIG.as_bytes()
@@ -136,7 +117,7 @@ impl Config {
136 117
         } else {
137 118
             // try to write a default config file
138 119
 
139
-            match Config::create_config_file(
120
+            match create_config_file(
140 121
                 config_dir,
141 122
                 &sources_file,
142 123
                 DEFAULT_TILE_SOURCES.as_bytes()
@@ -220,6 +201,14 @@ impl Config {
220 201
                     }
221 202
                 };
222 203
 
204
+                let open_last_session = {
205
+                    match table.get("open_last_session") {
206
+                        Some(&Value::Boolean(x)) => x,
207
+                        Some(_) => return Err("open_last_session has to be a boolean.".to_string()),
208
+                        None => false,
209
+                    }
210
+                };
211
+
223 212
                 Ok(
224 213
                     Config {
225 214
                         tile_cache_dir,
@@ -229,6 +218,7 @@ impl Config {
229 218
                         fps,
230 219
                         use_network,
231 220
                         async,
221
+                        open_last_session,
232 222
                     }
233 223
                 )
234 224
             },
@@ -361,8 +351,56 @@ impl Config {
361 351
     pub fn async(&self) -> bool {
362 352
         self.async
363 353
     }
354
+
355
+    pub fn open_last_session(&self) -> bool {
356
+        self.open_last_session
357
+    }
358
+}
359
+
360
+fn create_config_file<P: AsRef<Path> + Debug>(dir_path: P, file_path: P, contents: &[u8]) -> Result<(), String> {
361
+    if !dir_path.as_ref().is_dir() {
362
+        if let Err(err) = ::std::fs::create_dir_all(&dir_path) {
363
+            return Err(format!("failed to create config directory ({:?}): {}",
364
+                dir_path,
365
+                err
366
+            ));
367
+        }
368
+    }
369
+
370
+    let mut file = File::create(&file_path)
371
+        .map_err(|err| format!("failed to create config file {:?}: {}", &file_path, err))?;
372
+
373
+    file.write_all(contents)
374
+        .map_err(|err| format!(
375
+            "failed to write contents to config file {:?}: {}",
376
+            &file_path,
377
+            err
378
+        ))
379
+}
380
+
381
+pub fn read_last_session() -> Result<Session, String> {
382
+    let session_path = {
383
+        let config_dir = PROJ_DIRS.config_dir();
384
+        let mut path = PathBuf::from(config_dir);
385
+        path.push("last_session.toml");
386
+        path
387
+    };
388
+    Session::from_toml_file(session_path)
364 389
 }
365 390
 
391
+pub fn save_session(session: &Session) -> Result<(), String>
392
+{
393
+    let config_dir = PROJ_DIRS.config_dir();
394
+    let session_path = {
395
+        let mut path = PathBuf::from(config_dir);
396
+        path.push("last_session.toml");
397
+        path
398
+    };
399
+    let contents = session.to_toml_string();
400
+    create_config_file(config_dir, &session_path, contents.as_bytes())
401
+}
402
+
403
+
366 404
 #[cfg(test)]
367 405
 mod tests {
368 406
     use config::*;

+ 26
- 0
src/main.rs 查看文件

@@ -28,6 +28,7 @@ pub mod map_view_gl;
28 28
 pub mod marker_layer;
29 29
 pub mod program;
30 30
 pub mod search;
31
+pub mod session;
31 32
 pub mod texture;
32 33
 pub mod tile;
33 34
 pub mod tile_atlas;
@@ -240,6 +241,16 @@ fn run() -> Result<(), Box<Error>> {
240 241
         )
241 242
     };
242 243
 
244
+    if config.open_last_session() {
245
+        if let Ok(session) = config::read_last_session().as_ref() {
246
+            map.restore_session(session);
247
+
248
+            if let Some(ref tile_source) = session.tile_source {
249
+                sources.switch_to_name(tile_source);
250
+            }
251
+        }
252
+    }
253
+
243 254
     let mut input_state = InputState {
244 255
         mouse_position: (0.0, 0.0),
245 256
         mouse_pressed: false,
@@ -380,6 +391,12 @@ fn run() -> Result<(), Box<Error>> {
380 391
         }
381 392
     }
382 393
 
394
+    if config.open_last_session() {
395
+        let mut session = map.to_session();
396
+        session.tile_source = Some(sources.current_name().to_string());
397
+        config::save_session(&session)?;
398
+    }
399
+
383 400
     Ok(())
384 401
 }
385 402
 
@@ -424,4 +441,13 @@ impl<'a> TileSources<'a> {
424 441
     pub fn switch_to_prev(&mut self) {
425 442
         self.current_index = (self.current_index + self.sources.len().saturating_sub(1)) % self.sources.len();
426 443
     }
444
+
445
+    pub fn switch_to_name(&mut self, name: &str) {
446
+        for (index, &(ref n, _)) in self.sources.iter().enumerate() {
447
+            if n == name {
448
+                self.current_index = index;
449
+                break;
450
+            }
451
+        }
452
+    }
427 453
 }

+ 15
- 0
src/map_view_gl.rs 查看文件

@@ -2,6 +2,7 @@ use context::Context;
2 2
 use coord::{MapCoord, ScreenCoord};
3 3
 use map_view::MapView;
4 4
 use marker_layer::MarkerLayer;
5
+use session::Session;
5 6
 use tile_layer::TileLayer;
6 7
 use tile_source::TileSource;
7 8
 
@@ -149,4 +150,18 @@ impl MapViewGl {
149 150
         self.map_view.move_pixel(delta_x, delta_y);
150 151
         self.map_view.center.normalize_xy();
151 152
     }
153
+
154
+    pub fn restore_session(&mut self, session: &Session) {
155
+        self.map_view.center = session.view_center;
156
+        self.map_view.center.normalize_xy();
157
+        self.map_view.zoom = MIN_ZOOM_LEVEL.max(MAX_ZOOM_LEVEL.min(session.zoom));
158
+    }
159
+
160
+    pub fn to_session(&self) -> Session {
161
+        Session {
162
+            view_center: self.map_view.center,
163
+            zoom: self.map_view.zoom,
164
+            tile_source: None,
165
+        }
166
+    }
152 167
 }

+ 91
- 0
src/session.rs 查看文件

@@ -0,0 +1,91 @@
1
+use coord::MapCoord;
2
+use std::fs::File;
3
+use std::io::Read;
4
+use std::path::Path;
5
+use toml;
6
+use toml::Value;
7
+use toml::value::Table;
8
+
9
+
10
+#[derive(Debug)]
11
+pub struct Session {
12
+    pub view_center: MapCoord,
13
+    pub zoom: f64,
14
+    pub tile_source: Option<String>,
15
+}
16
+
17
+impl Session {
18
+    pub fn from_toml_file<P: AsRef<Path>>(path: P) -> Result<Session, String> {
19
+        let mut file = File::open(&path).map_err(|e| format!("{}", e))?;
20
+
21
+        let mut content = String::new();
22
+        file.read_to_string(&mut content).map_err(|e| format!("{}", e))?;
23
+
24
+        Session::from_toml_str(&content)
25
+    }
26
+
27
+    pub fn from_toml_str(toml_str: &str) -> Result<Session, String> {
28
+        match toml_str.parse::<Value>() {
29
+            Ok(Value::Table(ref table)) => {
30
+
31
+                let view = match table.get("view") {
32
+                    Some(&Value::Table(ref table)) => table,
33
+                    Some(_) => return Err("view has to be a table.".to_string()),
34
+                    None => return Err("view table is missing.".to_string()),
35
+                };
36
+
37
+                let x = match view.get("x") {
38
+                    Some(&Value::Float(x)) => x,
39
+                    Some(&Value::Integer(x)) => x as f64,
40
+                    Some(_) => return Err("x has to be a number.".to_string()),
41
+                    None => return Err("x position is missing.".to_string()),
42
+                };
43
+
44
+                let y = match view.get("y") {
45
+                    Some(&Value::Float(y)) => y,
46
+                    Some(&Value::Integer(y)) => y as f64,
47
+                    Some(_) => return Err("y has to be a number.".to_string()),
48
+                    None => return Err("y position is missing.".to_string()),
49
+                };
50
+
51
+                let zoom = match view.get("zoom") {
52
+                    Some(&Value::Float(z)) => z,
53
+                    Some(&Value::Integer(z)) => z as f64,
54
+                    Some(_) => return Err("zoom has to be a number.".to_string()),
55
+                    None => return Err("zoom value is missing.".to_string()),
56
+                };
57
+
58
+                let tile_source = match view.get("tile_source") {
59
+                    Some(&Value::String(ref s)) => Some(s.clone()),
60
+                    Some(_) => return Err("tile_source has to be a string.".to_string()),
61
+                    None => None,
62
+                };
63
+
64
+                Ok(
65
+                    Session {
66
+                        view_center: MapCoord::new(x, y),
67
+                        zoom,
68
+                        tile_source,
69
+                    }
70
+                )
71
+            },
72
+            Ok(_) => Err("TOML file has invalid structure. Expected a Table as the top-level element.".to_string()),
73
+            Err(e) => Err(format!("{}", e)),
74
+        }
75
+    }
76
+
77
+    pub fn to_toml_string(&self) -> String {
78
+        let mut view = Table::new();
79
+        view.insert("x".to_string(), Value::Float(self.view_center.x));
80
+        view.insert("y".to_string(), Value::Float(self.view_center.y));
81
+        view.insert("zoom".to_string(), Value::Float(self.zoom));
82
+        if let Some(ref tile_source) = self.tile_source {
83
+            view.insert("tile_source".to_string(), Value::String(tile_source.clone()));
84
+        }
85
+
86
+        let mut root = Table::new();
87
+        root.insert("view".to_string(), Value::Table(view));
88
+
89
+        toml::ser::to_string_pretty(&Value::Table(root)).unwrap()
90
+    }
91
+}