Repository: pop-os/popsicle (language: rust)

Issue:
Add margins to stack
Add a 12px margin to the stack to give a little breathing room.


Discussion / hints:
Do GTK pixel margins scale with HiDPI environments? I've always been partial to using CSS for setting margins / padding / borders due to `em` units scaling better on web pages, due to being calculated based on relative font sizes.
GTK is fully HiDPI out of the box. Ems make sense if what you actually want is typographic spacing, but in GTK we typically use display pixels, which are then scaled by the display's scaling factor. We usually want a 12px spacing inside a window, for example, so that widgets don't bump up against the edge. We don't want to scale that up if someone changes their system font because it's unrelated to type. It will automatically scale by the GTK scaling factor for the display, though.

Fix patch:
diff --git a/README.md b/README.md
index bbaf28f..af8c804 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,17 @@
 # Popsicle
 
-USB Flasher
+Popsicle is a Linux utility for flashing multiple USB devices in parallel, written in [Rust](https://www.rust-lang.org/en-US/).
+
+## Build Dependencies
+
+If building the GTK front end, you will be required to install the development dependencies for GTK, usually named `libgtk-3-dev`. No other dependencies are required to build the CLI or GTK front ends, besides Rust's `cargo` utility.
+
+For those who need to vendor Cargo's crate dependencies which are fetched from [Crates.io](https://crates.io/), you will need to install [cargo-vendor](https://github.com/alexcrichton/cargo-vendor), and then run `make vendor`.
+
+## Installation Instructions
+
+ A makefile is included for simply building and installing all required files into the system. You may either build both the CLI and GTK workspace, just the CLI workspace, or just the GTK workspace.
+
+- `make cli && sudo make install-cli` will build and install just the CLI workspace
+- `make gtk && sudo make install-gtk` will build and install just the GTK workspace
+- `make && sudo make install` will build and install both the CLI and GTK workspaces
\ No newline at end of file
diff --git a/cli/src/main.rs b/cli/src/main.rs
index 84456e3..16f8599 100644
--- a/cli/src/main.rs
+++ b/cli/src/main.rs
@@ -7,10 +7,10 @@ extern crate popsicle;
 
 use clap::{App, Arg};
 use pbr::{MultiBar, ProgressBar, Units};
-use std::{process, thread};
 use std::cell::RefCell;
 use std::io::{self, Write};
 use std::sync::Arc;
+use std::{process, thread};
 
 use popsicle::{DiskError, Image, Mount};
 
diff --git a/gtk/src/app/content/devices.rs b/gtk/src/app/content/devices.rs
new file mode 100644
index 0000000..a87fc68
--- /dev/null
+++ b/gtk/src/app/content/devices.rs
@@ -0,0 +1,37 @@
+use super::View;
+use gtk::*;
+
+pub struct DevicesView {
+    pub view:       View,
+    pub list:       ListBox,
+    pub select_all: CheckButton,
+}
+
+impl DevicesView {
+    pub fn new() -> DevicesView {
+        let select_all = CheckButton::new_with_label("Select all");
+        let list = ListBox::new();
+        list.get_style_context().map(|c| c.add_class("devices"));
+
+        let list_box = Box::new(Orientation::Vertical, 0);
+        list_box.pack_start(&select_all, false, false, 0);
+        list_box.pack_start(&Separator::new(Orientation::Horizontal), false, false, 0);
+        list_box.pack_start(&list, true, true, 0);
+
+        let select_scroller = ScrolledWindow::new(None, None);
+        select_scroller.add(&list_box);
+
+        let view = View::new(
+            "drive-removable-media-usb",
+            "Select Drives",
+            "Flashing will erase all data on the selected drives.",
+            |right_panel| right_panel.pack_start(&select_scroller, true, true, 0),
+        );
+
+        DevicesView {
+            view,
+            list,
+            select_all,
+        }
+    }
+}
diff --git a/gtk/src/app/content/error.rs b/gtk/src/app/content/error.rs
new file mode 100644
index 0000000..5690c01
--- /dev/null
+++ b/gtk/src/app/content/error.rs
@@ -0,0 +1,13 @@
+use super::View;
+
+pub struct ErrorView {
+    pub view: View,
+}
+
+impl ErrorView {
+    pub fn new() -> ErrorView {
+        ErrorView {
+            view: View::new("dialog-error", "Critical Error Occurred", "", |_| ()),
+        }
+    }
+}
diff --git a/gtk/src/app/content/flashing.rs b/gtk/src/app/content/flashing.rs
new file mode 100644
index 0000000..19746fe
--- /dev/null
+++ b/gtk/src/app/content/flashing.rs
@@ -0,0 +1,27 @@
+use super::View;
+use gtk::*;
+
+pub struct FlashView {
+    pub view:          View,
+    pub progress_list: Grid,
+}
+
+impl FlashView {
+    pub fn new() -> FlashView {
+        let progress_list = Grid::new();
+        let progress_scroller = ScrolledWindow::new(None, None);
+        progress_scroller.add(&progress_list);
+
+        let view = View::new(
+            "drive-removable-media-usb",
+            "Flashing Devices",
+            "Do not unplug devices while they are being flashed.",
+            |right_panel| right_panel.pack_start(&progress_scroller, true, true, 0),
+        );
+
+        FlashView {
+            view,
+            progress_list,
+        }
+    }
+}
diff --git a/gtk/src/ui/content/images.rs b/gtk/src/app/content/images.rs
similarity index 58%
rename from gtk/src/ui/content/images.rs
rename to gtk/src/app/content/images.rs
index 1537078..7a60328 100644
--- a/gtk/src/ui/content/images.rs
+++ b/gtk/src/app/content/images.rs
@@ -1,8 +1,9 @@
+use super::View;
 use gtk::*;
 use pango::EllipsizeMode;
 
 pub struct ImageView {
-    pub container:         Box,
+    pub view:              View,
     pub chooser_container: Stack,
     pub chooser:           Button,
     pub image_path:        Label,
@@ -12,24 +13,9 @@ pub struct ImageView {
 
 impl ImageView {
     pub fn new() -> ImageView {
-        let image = Image::new_from_icon_name("application-x-cd-image", 6);
-        image.set_valign(Align::Start);
-
-        let topic = Label::new("Choose an Image");
-        topic.set_halign(Align::Start);
-        topic.get_style_context().map(|c| c.add_class("h2"));
-
-        let description = Label::new(
-            "Select the .iso or .img that you want to flash. You can also plug your USB drives in \
-             now.",
-        );
-        description.set_line_wrap(true);
-        description.set_halign(Align::Start);
-        description.get_style_context().map(|c| c.add_class("desc"));
-
         let chooser = Button::new_with_label("Choose Image");
         chooser.set_halign(Align::Center);
-        chooser.set_halign(Align::Center);
+        chooser.set_margin_bottom(6);
 
         let image_path = Label::new("No image selected");
         image_path.set_ellipsize(EllipsizeMode::End);
@@ -63,35 +49,34 @@ impl ImageView {
         hash_container
             .get_style_context()
             .map(|c| c.add_class("hash-box"));
-        hash_container.pack_start(&hash, false, false, 0);
-        hash_container.pack_start(&hash_label, true, true, 0);
+        {
+            let label = Label::new("Hash:");
+            label.set_margin_right(6);
+            hash_container.pack_start(&label, false, false, 0);
+            hash_container.pack_start(&hash, false, false, 0);
+            hash_container.pack_start(&hash_label, true, true, 0);
+        }
 
         let chooser_container = Stack::new();
         chooser_container.add_named(&button_box, "chooser");
         chooser_container.add_named(&spinner_box, "loader");
         chooser_container.set_visible_child_name("chooser");
+        chooser_container.set_margin_top(12);
+        chooser_container.set_margin_bottom(24);
 
-        let left_panel = Box::new(Orientation::Vertical, 0);
-        left_panel
-            .get_style_context()
-            .map(|c| c.add_class("left-panel"));
-        left_panel.pack_start(&image, false, false, 0);
-
-        let right_panel = Box::new(Orientation::Vertical, 0);
-        right_panel
-            .get_style_context()
-            .map(|c| c.add_class("right-panel"));
-        right_panel.pack_start(&topic, false, false, 0);
-        right_panel.pack_start(&description, false, false, 0);
-        right_panel.pack_start(&chooser_container, true, false, 0);
-        right_panel.pack_start(&hash_container, false, false, 0);
-
-        let container = Box::new(Orientation::Horizontal, 5);
-        container.pack_start(&left_panel, false, false, 0);
-        container.pack_start(&right_panel, true, true, 0);
+        let view = View::new(
+            "application-x-cd-image",
+            "Choose an Image",
+            "Select the .iso or .img that you want to flash. You can also plug your USB drives in \
+             now.",
+            |right_panel| {
+                right_panel.pack_start(&chooser_container, true, false, 0);
+                right_panel.pack_start(&hash_container, false, false, 0);
+            },
+        );
 
         ImageView {
-            container,
+            view,
             chooser_container,
             chooser,
             image_path,
diff --git a/gtk/src/ui/content/mod.rs b/gtk/src/app/content/mod.rs
similarity index 70%
rename from gtk/src/ui/content/mod.rs
rename to gtk/src/app/content/mod.rs
index 287fd8e..7b51ff1 100644
--- a/gtk/src/ui/content/mod.rs
+++ b/gtk/src/app/content/mod.rs
@@ -3,15 +3,19 @@ mod error;
 mod flashing;
 mod images;
 mod summary;
+mod view;
 
 pub use self::devices::DevicesView;
 pub use self::error::ErrorView;
 pub use self::flashing::FlashView;
 pub use self::images::ImageView;
 pub use self::summary::SummaryView;
+pub use self::view::View;
 
 use gtk::*;
 
+use super::set_margins;
+
 pub struct Content {
     pub container:    Stack,
     pub image_view:   ImageView,
@@ -24,6 +28,7 @@ pub struct Content {
 impl Content {
     pub fn new() -> Content {
         let container = Stack::new();
+        set_margins(&container, 12);
 
         let image_view = ImageView::new();
         let devices_view = DevicesView::new();
@@ -31,11 +36,11 @@ impl Content {
         let summary_view = SummaryView::new();
         let error_view = ErrorView::new();
 
-        container.add_named(&image_view.container, "image");
-        container.add_named(&devices_view.container, "devices");
-        container.add_named(&flash_view.container, "flash");
-        container.add_named(&summary_view.container, "summary");
-        container.add_named(&error_view.container, "error");
+        container.add_named(&image_view.view.container, "image");
+        container.add_named(&devices_view.view.container, "devices");
+        container.add_named(&flash_view.view.container, "flash");
+        container.add_named(&summary_view.view.container, "summary");
+        container.add_named(&error_view.view.container, "error");
         container.set_visible_child_name("image");
 
         Content {
diff --git a/gtk/src/app/content/summary.rs b/gtk/src/app/content/summary.rs
new file mode 100644
index 0000000..f8b9a65
--- /dev/null
+++ b/gtk/src/app/content/summary.rs
@@ -0,0 +1,25 @@
+use super::View;
+use gtk::*;
+
+pub struct SummaryView {
+    pub view: View,
+    pub list: ListBox,
+}
+
+impl SummaryView {
+    pub fn new() -> SummaryView {
+        let list = ListBox::new();
+        list.set_visible(false);
+
+        let view = View::new(
+            "process-completed",
+            "Flashing Completed",
+            "",
+            |right_panel| {
+                right_panel.pack_start(&list, true, true, 0);
+            },
+        );
+
+        SummaryView { view, list }
+    }
+}
diff --git a/gtk/src/app/content/view.rs b/gtk/src/app/content/view.rs
new file mode 100644
index 0000000..4ccc1b6
--- /dev/null
+++ b/gtk/src/app/content/view.rs
@@ -0,0 +1,60 @@
+use gtk::prelude::*;
+use gtk::{self, Align, Image, Label, Orientation};
+
+pub struct View {
+    pub container:   gtk::Box,
+    pub icon:        Image,
+    pub topic:       Label,
+    pub description: Label,
+    pub panel:       gtk::Box,
+}
+
+impl View {
+    pub fn new<F: Fn(&gtk::Box)>(
+        icon: &str,
+        topic: &str,
+        description: &str,
+        configure_panel: F,
+    ) -> View {
+        let icon = Image::new_from_icon_name(icon, 6);
+        icon.set_valign(Align::Start);
+
+        let topic = Label::new(topic);
+        topic.set_halign(Align::Start);
+        topic.get_style_context().map(|c| c.add_class("h2"));
+        topic.set_margin_bottom(6);
+
+        let description = Label::new(description);
+        description.set_line_wrap(true);
+        description.set_halign(Align::Start);
+        description.get_style_context().map(|c| c.add_class("desc"));
+        description.set_margin_bottom(6);
+
+        let left_panel = gtk::Box::new(Orientation::Vertical, 0);
+        left_panel
+            .get_style_context()
+            .map(|c| c.add_class("left-panel"));
+        left_panel.pack_start(&icon, false, false, 0);
+
+        let right_panel = gtk::Box::new(Orientation::Vertical, 0);
+        right_panel
+            .get_style_context()
+            .map(|c| c.add_class("right-panel"));
+        right_panel.pack_start(&topic, false, false, 0);
+        right_panel.pack_start(&description, false, false, 0);
+
+        let container = gtk::Box::new(Orientation::Horizontal, 12);
+        container.pack_start(&left_panel, false, false, 0);
+        container.pack_start(&right_panel, true, true, 0);
+
+        configure_panel(&right_panel);
+
+        View {
+            container,
+            icon,
+            topic,
+            description,
+            panel: right_panel,
+        }
+    }
+}
diff --git a/gtk/src/ui/dialogs.rs b/gtk/src/app/dialogs.rs
similarity index 100%
rename from gtk/src/ui/dialogs.rs
rename to gtk/src/app/dialogs.rs
diff --git a/gtk/src/ui/hash.rs b/gtk/src/app/hash.rs
similarity index 100%
rename from gtk/src/ui/hash.rs
rename to gtk/src/app/hash.rs
diff --git a/gtk/src/ui/header.rs b/gtk/src/app/header.rs
similarity index 100%
rename from gtk/src/ui/header.rs
rename to gtk/src/app/header.rs
diff --git a/gtk/src/app/misc.rs b/gtk/src/app/misc.rs
new file mode 100644
index 0000000..6187c9a
--- /dev/null
+++ b/gtk/src/app/misc.rs
@@ -0,0 +1,8 @@
+use gtk::WidgetExt;
+
+pub fn set_margins<W: WidgetExt>(widget: &W, value: i32) {
+    widget.set_margin_top(value);
+    widget.set_margin_bottom(value);
+    widget.set_margin_left(value);
+    widget.set_margin_right(value);
+}
diff --git a/gtk/src/ui/mod.rs b/gtk/src/app/mod.rs
similarity index 53%
rename from gtk/src/ui/mod.rs
rename to gtk/src/app/mod.rs
index 0742007..cfdc651 100644
--- a/gtk/src/ui/mod.rs
+++ b/gtk/src/app/mod.rs
@@ -1,29 +1,25 @@
 mod content;
-mod state;
 mod dialogs;
 mod hash;
 mod header;
+mod misc;
+mod state;
 
 use self::content::Content;
 pub use self::dialogs::OpenDialog;
 use self::header::Header;
-pub use self::state::{BufferingData, Connect};
+pub use self::misc::*;
+pub use self::state::{Connect, State};
 
 // TODO: Use AtomicU64 / Bool when https://github.com/rust-lang/rust/issues/32976 is stable.
 
-use std::cell::{Cell, RefCell};
-use std::mem;
 use std::path::PathBuf;
 use std::process;
-use std::sync::{Arc, Mutex};
-use std::sync::atomic::AtomicUsize;
+use std::sync::Arc;
 use std::sync::mpsc::Sender;
-use std::thread::JoinHandle;
-use std::time::Instant;
 
 use gtk;
 use gtk::*;
-use popsicle::DiskError;
 
 const CSS: &str = include_str!("ui.css");
 
@@ -34,48 +30,6 @@ pub struct App {
     pub state:   Arc<State>,
 }
 
-/// Contains all of the state that needs to be shared across the program's lifetime.
-pub struct State {
-    /// Contains all of the progress bars in the flash view.
-    pub bars: RefCell<Vec<(ProgressBar, Label)>>,
-    /// Contains a list of devices detected, and their check buttons.
-    pub devices: Mutex<Vec<(String, CheckButton)>>,
-    /// Holds the task threads that write the image to each device.
-    /// The handles may contain errors when joined, for printing on the summary page.
-    pub task_handles: Mutex<Vec<JoinHandle<Result<(), DiskError>>>>,
-    /// Contains progress data regarding each active flash task -- namely the progress.
-    pub tasks: Mutex<Vec<FlashTask>>,
-    /// Stores an integer which defines the currently-active view.
-    pub view: Cell<u8>,
-    /// Stores the time when the flashing process began.
-    pub start: RefCell<Instant>,
-    pub buffer: Arc<BufferingData>,
-    pub image_sender: Sender<PathBuf>,
-    pub image_length: Cell<usize>,
-}
-
-impl State {
-    fn new(image_sender: Sender<PathBuf>) -> State {
-        State {
-            bars: RefCell::new(Vec::new()),
-            devices: Mutex::new(Vec::new()),
-            task_handles: Mutex::new(Vec::new()),
-            tasks: Mutex::new(Vec::new()),
-            view: Cell::new(0),
-            start: RefCell::new(unsafe { mem::uninitialized() }),
-            buffer: Arc::new(BufferingData::new()),
-            image_sender,
-            image_length: Cell::new(0),
-        }
-    }
-}
-
-pub struct FlashTask {
-    progress: Arc<AtomicUsize>,
-    previous: Arc<Mutex<[usize; 7]>>,
-    finished: Arc<AtomicUsize>,
-}
-
 impl App {
     pub fn new(sender: Sender<PathBuf>) -> App {
         // Initialize GTK before proceeding.
@@ -103,8 +57,9 @@ impl App {
         window.set_title("Popsicle");
         // Set the window manager class.
         window.set_wmclass("popsicle", "Popsicle");
+        // The default size of the window to create.
+        window.set_default_size(500, 250);
         // The icon the app will display.
-        window.set_default_size(400, -1);
         Window::set_default_icon_name("iconname");
         // Add the content to the window.
         window.add(&content.container);
diff --git a/gtk/src/ui/state.rs b/gtk/src/app/state.rs
similarity index 86%
rename from gtk/src/ui/state.rs
rename to gtk/src/app/state.rs
index fd7581d..18ff86f 100644
--- a/gtk/src/ui/state.rs
+++ b/gtk/src/app/state.rs
@@ -1,21 +1,79 @@
-use super::{hash, App, FlashTask, OpenDialog};
 use super::super::BlockDevice;
+use super::{hash, App, OpenDialog};
 
+use image::BufferingData;
+use std::cell::{Cell, RefCell};
 use std::mem;
 use std::ops::{Deref, DerefMut};
 use std::path::{Path, PathBuf};
-use std::sync::{Arc, Mutex};
 use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::mpsc::Sender;
+use std::sync::{Arc, Mutex};
 use std::thread;
+use std::thread::JoinHandle;
 use std::time::Instant;
 
 use gtk;
 use gtk::*;
 use popsicle::{self, DiskError};
 
+/// Contains all of the state that needs to be shared across the program's lifetime.
+pub struct State {
+    /// Contains all of the progress bars in the flash view.
+    pub bars: RefCell<Vec<(ProgressBar, Label)>>,
+    /// Contains the disk image that is loaded into memory and shared across threads.
+    pub buffer: Arc<BufferingData>,
+    /// Contains a list of devices detected, and their check buttons.
+    pub devices: Mutex<Vec<(String, CheckButton)>>,
+    /// Useful for storing the size of the image that was loaded.
+    pub image_length: Cell<usize>,
+    /// Signals to load a new disk image into the `buffer` field.
+    pub image_sender: Sender<PathBuf>,
+    /// Stores the time when the flashing process began.
+    pub start: RefCell<Instant>,
+    /// Holds the task threads that write the image to each device.
+    /// The handles may contain errors when joined, for printing on the summary page.
+    pub task_handles: Mutex<Vec<JoinHandle<Result<(), DiskError>>>>,
+    /// Contains progress data regarding each active flash task -- namely the progress.
+    pub tasks: Mutex<Vec<FlashTask>>,
+    /// Stores an integer which defines the currently-active view.
+    pub view: Cell<u8>,
+}
+
+impl State {
+    /// Initailizes a new structure for managing the state of the application.
+    pub fn new(image_sender: Sender<PathBuf>) -> State {
+        State {
+            bars: RefCell::new(Vec::new()),
+            devices: Mutex::new(Vec::new()),
+            task_handles: Mutex::new(Vec::new()),
+            tasks: Mutex::new(Vec::new()),
+            view: Cell::new(0),
+            start: RefCell::new(unsafe { mem::uninitialized() }),
+            buffer: Arc::new(BufferingData::new()),
+            image_sender,
+            image_length: Cell::new(0),
+        }
+    }
+}
+
+pub struct FlashTask {
+    progress: Arc<AtomicUsize>,
+    previous: Arc<Mutex<[usize; 7]>>,
+    finished: Arc<AtomicUsize>,
+}
+
 macro_rules! try_or_error {
-    ($act: expr, $view: expr, $back: expr, $next: expr,
-     $stack: ident, $error: ident, $msg: expr, $val: expr) => {
+    (
+        $act: expr,
+        $view: expr,
+        $back: expr,
+        $next: expr,
+        $stack: ident,
+        $error: ident,
+        $msg: expr,
+        $val: expr
+    ) => {
         match $act {
             Ok(value) => value,
             Err(why) => {
@@ -35,20 +93,6 @@ macro_rules! try_or_error {
     };
 }
 
-pub struct BufferingData {
-    pub data:  Mutex<(PathBuf, Vec<u8>)>,
-    pub state: AtomicUsize,
-}
-
-impl BufferingData {
-    pub fn new() -> BufferingData {
-        BufferingData {
-            data:  Mutex::new((PathBuf::new(), Vec::new())),
-            state: 0.into(),
-        }
-    }
-}
-
 pub struct Connected(App);
 
 impl Connected {
@@ -136,6 +180,9 @@ impl Connect for App {
                     stack.set_transition_type(StackTransitionType::SlideRight);
                     stack.set_visible_child_name("image");
                     back.set_label("Cancel");
+                    back.get_style_context().map(|c| {
+                        c.remove_class("back-button");
+                    });
                     next.set_label("Next");
                     next.set_sensitive(true);
                     next.get_style_context().map(|c| {
@@ -159,7 +206,7 @@ impl Connect for App {
         let stack = self.content.container.clone();
         let summary_grid = self.content.flash_view.progress_list.clone();
         let state = self.state.clone();
-        let error = self.content.error_view.buffer.clone();
+        let error = self.content.error_view.view.description.clone();
 
         next.connect_clicked(move |next| {
             let device_list = &state.devices;
@@ -186,6 +233,9 @@ impl Connect for App {
                 // Move to device selection screen
                 0 => {
                     back.set_label("Back");
+                    back.get_style_context().map(|c| {
+                        c.add_class("back-button");
+                    });
                     next.set_label("Flash");
                     next.get_style_context().map(|c| {
                         c.remove_class("suggested-action");
@@ -196,7 +246,6 @@ impl Connect for App {
                     // Remove all but the first row
                     list.get_children()
                         .into_iter()
-                        .skip(1)
                         .for_each(|widget| widget.destroy());
 
                     let mut devices = vec![];
@@ -248,6 +297,10 @@ impl Connect for App {
                 }
                 // Begin the device flashing process
                 1 => {
+                    back.get_style_context().map(|c| {
+                        c.remove_class("back-button");
+                    });
+
                     let device_list = try_or_error!(
                         device_list.lock(),
                         state.view,
@@ -258,7 +311,9 @@ impl Connect for App {
                         "device list mutex lock failure",
                         ()
                     );
-                    let devs = device_list.iter().map(|x| x.0.clone());
+                    let devs = device_list.iter()
+                        .filter(|x| x.1.get_active())
+                        .map(|x| x.0.clone());
 
                     let mounts = try_or_error!(
                         popsicle::Mount::all(),
@@ -356,7 +411,7 @@ impl Connect for App {
                         label.set_justify(Justification::Right);
                         label
                             .get_style_context()
-                            .map(|c| c.add_class("progress-label"));
+                            .map(|c| c.add_class("bold"));
                         let bar_label = Label::new("");
                         bar_label.set_halign(Align::Center);
                         let bar_container = Box::new(Orientation::Vertical, 0);
@@ -412,25 +467,23 @@ impl Connect for App {
         let back = self.header.back.clone();
         let next = self.header.next.clone();
         let stack = self.content.container.clone();
-        let error = self.content.error_view.buffer.clone();
+        let error = self.content.error_view.view.description.clone();
         let state = self.state.clone();
         all.connect_clicked(move |all| {
-            if all.get_active() {
-                let devices = try_or_error!(
-                    state.devices.lock(),
-                    state.view,
-                    back,
-                    next,
-                    stack,
-                    error,
-                    "devices mutex lock failure",
-                    ()
-                );
+            let devices = try_or_error!(
+                state.devices.lock(),
+                state.view,
+                back,
+                next,
+                stack,
+                error,
+                "devices mutex lock failure",
+                ()
+            );
 
-                devices
-                    .iter()
-                    .for_each(|&(_, ref device)| device.set_active(true));
-            }
+            devices
+                .iter()
+                .for_each(|&(_, ref device)| device.set_active(all.get_active()));
         });
     }
 
@@ -438,12 +491,12 @@ impl Connect for App {
         let stack = self.content.container.clone();
         let back = self.header.back.clone();
         let next = self.header.next.clone();
-        let description = self.content.summary_view.description.clone();
+        let description = self.content.summary_view.view.description.clone();
         let list = self.content.summary_view.list.clone();
         let state = self.state.clone();
         let image_label = self.content.image_view.image_path.clone();
         let chooser_container = self.content.image_view.chooser_container.clone();
-        let error = self.content.error_view.buffer.clone();
+        let error = self.content.error_view.view.description.clone();
 
         gtk::timeout_add(500, move || {
             let tasks = &state.tasks;
@@ -554,7 +607,7 @@ impl Connect for App {
                     stack,
                     error,
                     "task handles mutex lock failure",
-                    Continue(true)
+                    Continue(false)
                 );
 
                 let devices = try_or_error!(
@@ -565,7 +618,7 @@ impl Connect for App {
                     stack,
                     error,
                     "devices mutex lock failure",
-                    Continue(true)
+                    Continue(false)
                 );
 
                 let handle_iter = task_handles.deref_mut().drain(..);
diff --git a/gtk/src/app/ui.css b/gtk/src/app/ui.css
new file mode 100644
index 0000000..bddee47
--- /dev/null
+++ b/gtk/src/app/ui.css
@@ -0,0 +1,12 @@
+.h2 {
+	font-size: 1.25em;
+	font-weight: bold;
+}
+
+.bold {
+    font-weight: bold;    
+}
+
+.hash-box button, .hash-box entry {
+    border-radius: 0;
+}
\ No newline at end of file
diff --git a/gtk/src/image.rs b/gtk/src/image.rs
index 8aa22b3..c1ba96b 100644
--- a/gtk/src/image.rs
+++ b/gtk/src/image.rs
@@ -1,10 +1,30 @@
-use super::ui::BufferingData;
 use popsicle::Image;
 use std::io;
 use std::path::{Path, PathBuf};
-use std::sync::atomic::Ordering;
+use std::sync::Mutex;
+use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::mpsc::Receiver;
 
+/// A structure for buffering disk images in the background.
+pub struct BufferingData {
+    /// Stores the path of the image, and the image contents stored in memory.
+    pub data: Mutex<(PathBuf, Vec<u8>)>,
+    /// This field will determine if the `data` field is ready to be used.
+    pub state: AtomicUsize,
+}
+
+impl BufferingData {
+    pub fn new() -> BufferingData {
+        BufferingData {
+            data:  Mutex::new((PathBuf::new(), Vec::new())),
+            state: 0.into(),
+        }
+    }
+}
+
+/// An event loop that is meant to be run in a background thread, receiving image paths
+/// to load, and buffering those images into the application's shared `BufferingData`
+/// field.
 pub fn image_load_event_loop(path_receiver: Receiver<PathBuf>, buffer: &BufferingData) {
     while let Ok(path) = path_receiver.recv() {
         buffer.state.store(0b1, Ordering::SeqCst);
diff --git a/gtk/src/main.rs b/gtk/src/main.rs
index d32583c..1db7695 100644
--- a/gtk/src/main.rs
+++ b/gtk/src/main.rs
@@ -7,15 +7,15 @@ extern crate popsicle;
 extern crate pwd;
 extern crate sha3;
 
+mod app;
 mod block;
 mod image;
-mod ui;
 
+use app::{App, Connect};
 use std::env;
 use std::path::PathBuf;
 use std::sync::mpsc::channel;
 use std::thread;
-use ui::{App, Connect};
 
 pub use block::BlockDevice;
 
diff --git a/gtk/src/ui/content/devices.rs b/gtk/src/ui/content/devices.rs
deleted file mode 100644
index 2b4ab53..0000000
--- a/gtk/src/ui/content/devices.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-use gtk::*;
-
-pub struct DevicesView {
-    pub container:  Box,
-    pub list:       ListBox,
-    pub select_all: CheckButton,
-}
-
-impl DevicesView {
-    pub fn new() -> DevicesView {
-        let image = Image::new_from_icon_name("drive-removable-media-usb", 6);
-        image.set_valign(Align::Start);
-
-        let topic = Label::new("Select Drives");
-        topic.set_halign(Align::Start);
-        topic.get_style_context().map(|c| c.add_class("h2"));
-
-        let description = Label::new("Flashing will erase all data on the selected drives.");
-        description.set_line_wrap(true);
-        description.set_halign(Align::Start);
-        description.get_style_context().map(|c| c.add_class("desc"));
-
-        let select_all = CheckButton::new_with_label("Select all");
-        let list = ListBox::new();
-        list.insert(&select_all, -1);
-        list.get_style_context().map(|c| c.add_class("devices"));
-
-        let select_scroller = ScrolledWindow::new(None, None);
-        select_scroller.add(&list);
-
-        let left_panel = Box::new(Orientation::Vertical, 0);
-        left_panel
-            .get_style_context()
-            .map(|c| c.add_class("left-panel"));
-        left_panel.pack_start(&image, false, false, 0);
-
-        let right_panel = Box::new(Orientation::Vertical, 0);
-        right_panel
-            .get_style_context()
-            .map(|c| c.add_class("right-panel"));
-        right_panel.pack_start(&topic, false, false, 0);
-        right_panel.pack_start(&description, false, false, 0);
-        right_panel.pack_start(&select_scroller, true, true, 0);
-
-        let container = Box::new(Orientation::Horizontal, 0);
-        container.pack_start(&left_panel, false, false, 0);
-        container.pack_start(&right_panel, true, true, 0);
-
-        DevicesView {
-            container,
-            list,
-            select_all,
-        }
-    }
-}
diff --git a/gtk/src/ui/content/error.rs b/gtk/src/ui/content/error.rs
deleted file mode 100644
index 437d26a..0000000
--- a/gtk/src/ui/content/error.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-use gtk::*;
-
-pub struct ErrorView {
-    pub container: Box,
-    pub buffer:    TextBuffer,
-}
-
-impl ErrorView {
-    pub fn new() -> ErrorView {
-        let image = Image::new_from_icon_name("dialog-error", 6);
-        image.set_valign(Align::Start);
-
-        let topic = Label::new("Critical Error Occurred");
-        topic.set_halign(Align::Start);
-        topic.get_style_context().map(|c| c.add_class("h2"));
-
-        let description = TextView::new();
-        description.set_halign(Align::Start);
-        description.get_style_context().map(|c| c.add_class("desc"));
-
-        let left_panel = Box::new(Orientation::Vertical, 0);
-        left_panel
-            .get_style_context()
-            .map(|c| c.add_class("left-panel"));
-        left_panel.pack_start(&image, false, false, 0);
-
-        let right_panel = Box::new(Orientation::Vertical, 0);
-        right_panel
-            .get_style_context()
-            .map(|c| c.add_class("right-panel"));
-        right_panel.pack_start(&topic, false, false, 0);
-        right_panel.pack_start(&description, true, true, 0);
-
-        let container = Box::new(Orientation::Horizontal, 0);
-        container.pack_start(&left_panel, false, false, 0);
-        container.pack_start(&right_panel, true, true, 0);
-
-        ErrorView {
-            container,
-            buffer: description.get_buffer().unwrap(),
-        }
-    }
-}
diff --git a/gtk/src/ui/content/flashing.rs b/gtk/src/ui/content/flashing.rs
deleted file mode 100644
index 5bae938..0000000
--- a/gtk/src/ui/content/flashing.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-use gtk::*;
-
-pub struct FlashView {
-    pub container:     Box,
-    pub progress_list: Grid,
-}
-
-impl FlashView {
-    pub fn new() -> FlashView {
-        let image = Image::new_from_icon_name("drive-removable-media-usb", 6);
-        image.set_valign(Align::Start);
-
-        let topic = Label::new("Flashing Devices");
-        topic.set_halign(Align::Start);
-        topic.get_style_context().map(|c| c.add_class("h2"));
-
-        let spinner = Spinner::new();
-        spinner.start();
-        spinner.set_valign(Align::Start);
-
-        let description = Label::new("Do not unplug devices while they are being flashed.");
-        description.set_halign(Align::Start);
-
-        let progress_list = Grid::new();
-        let progress_scroller = ScrolledWindow::new(None, None);
-        progress_scroller.add(&progress_list);
-
-        let left_panel = Box::new(Orientation::Vertical, 0);
-        left_panel
-            .get_style_context()
-            .map(|c| c.add_class("left-panel"));
-        left_panel.pack_start(&image, false, false, 0);
-
-        let topic_container = Box::new(Orientation::Horizontal, 0);
-        topic_container.pack_start(&topic, false, false, 0);
-        topic_container.pack_start(&spinner, false, false, 0);
-
-        let right_panel = Box::new(Orientation::Vertical, 0);
-        right_panel
-            .get_style_context()
-            .map(|c| c.add_class("right-panel"));
-        right_panel.pack_start(&topic_container, false, false, 0);
-        right_panel.pack_start(&description, false, false, 0);
-        right_panel.pack_start(&progress_scroller, true, true, 0);
-
-        let container = Box::new(Orientation::Horizontal, 0);
-        container.pack_start(&left_panel, false, false, 0);
-        container.pack_start(&right_panel, true, true, 0);
-
-        FlashView {
-            container,
-            progress_list,
-        }
-    }
-}
diff --git a/gtk/src/ui/content/summary.rs b/gtk/src/ui/content/summary.rs
deleted file mode 100644
index beb1a30..0000000
--- a/gtk/src/ui/content/summary.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use gtk::*;
-
-pub struct SummaryView {
-    pub container:   Box,
-    pub description: Label,
-    pub list:        ListBox,
-}
-
-impl SummaryView {
-    pub fn new() -> SummaryView {
-        let image = Image::new_from_icon_name("process-completed", 6);
-        image.set_valign(Align::Start);
-
-        let topic = Label::new("Flashing Completed");
-        topic.set_halign(Align::Start);
-        topic.get_style_context().map(|c| c.add_class("h2"));
-
-        let description = Label::new("");
-        description.get_style_context().map(|c| c.add_class("desc"));
-        description.set_halign(Align::Start);
-
-        let list = ListBox::new();
-        list.set_visible(false);
-
-        let left_panel = Box::new(Orientation::Vertical, 0);
-        left_panel
-            .get_style_context()
-            .map(|c| c.add_class("left-panel"));
-        left_panel.pack_start(&image, false, false, 0);
-
-        let right_panel = Box::new(Orientation::Vertical, 0);
-        right_panel
-            .get_style_context()
-            .map(|c| c.add_class("right-panel"));
-        right_panel.pack_start(&topic, false, false, 0);
-        right_panel.pack_start(&description, false, false, 0);
-        right_panel.pack_start(&list, true, true, 0);
-
-        let container = Box::new(Orientation::Horizontal, 0);
-        container.pack_start(&left_panel, false, false, 0);
-        container.pack_start(&right_panel, true, true, 0);
-
-        SummaryView {
-            container,
-            description,
-            list,
-        }
-    }
-}
diff --git a/gtk/src/ui/ui.css b/gtk/src/ui/ui.css
deleted file mode 100644
index 0da61b6..0000000
--- a/gtk/src/ui/ui.css
+++ /dev/null
@@ -1,68 +0,0 @@
-.left-panel {
-    margin-right: .5em;
-}
-
-.left-panel image {
-    padding: .25em;
-}
-
-.right-panel {
-    padding: .5em;
-}
-
-spinner {
-    padding-left: .5em;
-}
-
-.h2 {
-	font-size: 1.25em;
-	font-weight: bold;
-	padding-bottom: 1em;
-}
-
-.bold {
-    font-weight: bold;    
-}
-
-.desc {
-    padding-bottom: 1em;
-}
-
-.hash-box button, .hash-box entry {
-    border-radius: 0;
-}
-
-.devices {
-    border-width:0.2em;
-    border-style: solid;
-    border-color: @theme_fg_color;
-}
-
-.devices > row:nth-child(1) {
-    border-bottom-width:0.2em;
-    border-style: solid;
-    border-color: @theme_fg_color;
-    font-weight: bold;
-}
-
-.progress-label {
-    font-weight: bold;
-    padding-right: 1em;
-}
-
-grid {
-    margin-top: 1em;
-    padding-top: .5em;
-    border-top-width: .2em;
-    border-top-style: solid;
-}
-
-box > spinner {
-    padding-top: .25em;
-    padding-right: .5em;
-}
-
-grid box, grid > label {
-    padding-top: .5em;
-    padding-bottom: .5em;
-}
\ No newline at end of file


Test patch: