diff --git a/neothesia-core/src/render/quad/mod.rs b/neothesia-core/src/render/quad/mod.rs
index dfbab7e3..1b127db0 100644
--- a/neothesia-core/src/render/quad/mod.rs
+++ b/neothesia-core/src/render/quad/mod.rs
@@ -79,6 +79,10 @@ impl<'a> QuadPipeline {
         &mut self.instances.data
     }
 
+    pub fn push(&mut self, quad: QuadInstance) {
+        self.instances.data.push(quad)
+    }
+
     pub fn prepare(&self, queue: &wgpu::Queue) {
         self.instances.update(queue);
     }
diff --git a/neothesia-core/src/utils/bbox.rs b/neothesia-core/src/utils/bbox.rs
new file mode 100644
index 00000000..39c1fc1a
--- /dev/null
+++ b/neothesia-core/src/utils/bbox.rs
@@ -0,0 +1,36 @@
+use super::{Point, Size};
+
+#[derive(Default, Clone, Copy)]
+pub struct Bbox {
+    pub pos: Point<f32>,
+    pub size: Size<f32>,
+}
+
+impl Bbox {
+    pub fn new(pos: Point<f32>, size: Size<f32>) -> Self {
+        Self { pos, size }
+    }
+
+    pub fn contains(&self, px: f32, py: f32) -> bool {
+        let Point { x, y } = self.pos;
+        let Size { w, h } = self.size;
+
+        (x..(x + w)).contains(&px) && (y..(y + h)).contains(&py)
+    }
+
+    pub fn x(&self) -> f32 {
+        self.pos.x
+    }
+
+    pub fn y(&self) -> f32 {
+        self.pos.y
+    }
+
+    pub fn w(&self) -> f32 {
+        self.size.w
+    }
+
+    pub fn h(&self) -> f32 {
+        self.size.h
+    }
+}
diff --git a/neothesia-core/src/utils/mod.rs b/neothesia-core/src/utils/mod.rs
index 111f79ef..c9e483f7 100644
--- a/neothesia-core/src/utils/mod.rs
+++ b/neothesia-core/src/utils/mod.rs
@@ -1,11 +1,20 @@
+pub mod bbox;
 pub mod resources;
 
+pub use bbox::Bbox;
+
 #[derive(Debug, Default, Clone, Copy)]
 pub struct Point<T> {
     pub x: T,
     pub y: T,
 }
 
+impl<T> Point<T> {
+    pub fn new(x: T, y: T) -> Self {
+        Self { x, y }
+    }
+}
+
 impl<T> From<(T, T)> for Point<T> {
     fn from((x, y): (T, T)) -> Self {
         Self { x, y }
@@ -54,6 +63,12 @@ pub struct Size<T> {
     pub h: T,
 }
 
+impl<T> Size<T> {
+    pub fn new(w: T, h: T) -> Self {
+        Self { w, h }
+    }
+}
+
 impl<T> From<(T, T)> for Size<T> {
     fn from((w, h): (T, T)) -> Self {
         Self { w, h }
diff --git a/neothesia/src/scene/playing_scene/top_bar.rs b/neothesia/src/scene/playing_scene/top_bar.rs
index a70b670e..d7785205 100644
--- a/neothesia/src/scene/playing_scene/top_bar.rs
+++ b/neothesia/src/scene/playing_scene/top_bar.rs
@@ -1,6 +1,9 @@
 use std::time::Duration;
 
-use neothesia_core::render::{QuadInstance, TextRenderer};
+use neothesia_core::{
+    render::{QuadInstance, QuadPipeline, TextRenderer},
+    utils::{Bbox, Point, Size},
+};
 use wgpu_jumpstart::Color;
 use winit::{
     dpi::PhysicalPosition,
@@ -24,29 +27,6 @@ enum Element {
     None,
 }
 
-#[derive(Default, Clone, Copy)]
-struct ElementInfo {
-    x: f32,
-    y: f32,
-    w: f32,
-    h: f32,
-}
-
-impl ElementInfo {
-    fn contains(&self, x: f32, y: f32) -> bool {
-        (self.x..(self.x + self.w)).contains(&x) && (self.y..(self.y + self.h)).contains(&y)
-    }
-
-    fn pos(&self) -> [f32; 2] {
-        [self.x, self.y]
-    }
-
-    fn size(&self) -> [f32; 2] {
-        [self.w, self.h]
-    }
-}
-
-#[derive(Default)]
 pub struct TopBar {
     height: f32,
     loop_tick_height: f32,
@@ -58,15 +38,82 @@ pub struct TopBar {
     pub loop_start: Duration,
     pub loop_end: Duration,
 
-    back_button: ElementInfo,
-    play_button: ElementInfo,
-    loop_button: ElementInfo,
-    loop_start_tick: ElementInfo,
-    loop_end_tick: ElementInfo,
+    back_button: Button,
+    play_button: Button,
+    loop_button: Button,
+    loop_start_tick: Bbox,
+    loop_end_tick: Bbox,
 
     pub loop_active: bool,
 }
 
+#[derive(Default)]
+struct Button {
+    bbox: Bbox,
+    element: Element,
+    is_hovered: bool,
+    icon: &'static str,
+}
+
+impl Button {
+    fn new(element: Element) -> Self {
+        Self {
+            element,
+            bbox: Bbox::new(Point::new(0.0, 0.0), Size::new(30.0, 30.0)),
+            is_hovered: false,
+            icon: "",
+        }
+    }
+
+    fn set_x(&mut self, x: f32) -> &mut Self {
+        self.bbox.pos.x = x;
+        self
+    }
+
+    fn set_y(&mut self, y: f32) -> &mut Self {
+        self.bbox.pos.y = y;
+        self
+    }
+
+    fn set_hovered(&mut self, hovered: bool) -> &mut Self {
+        self.is_hovered = hovered;
+        self
+    }
+
+    fn set_icon(&mut self, icon: &'static str) -> &mut Self {
+        self.icon = icon;
+        self
+    }
+
+    fn bbox_with_type(&self) -> (&Bbox, Element) {
+        (&self.bbox, self.element)
+    }
+
+    fn draw(&mut self, quad_pipeline: &mut QuadPipeline, text: &mut TextRenderer) {
+        let color = if self.is_hovered {
+            BUTTON_HOVER
+        } else {
+            BAR_BG
+        }
+        .into_linear_rgba();
+
+        quad_pipeline.push(QuadInstance {
+            position: self.bbox.pos.into(),
+            size: self.bbox.size.into(),
+            color,
+            border_radius: [5.0; 4],
+        });
+
+        let icon_size = 20.0;
+        text.queue_icon(
+            self.bbox.x() + (self.bbox.w() - icon_size) / 2.0,
+            self.bbox.y() + (self.bbox.h() - icon_size) / 2.0,
+            icon_size,
+            self.icon,
+        );
+    }
+}
+
 macro_rules! color_u8 {
     ($r: expr, $g: expr, $b: expr, $a: expr) => {
         Color::new($r as f32 / 255.0, $g as f32 / 255.0, $b as f32 / 255.0, 1.0)
@@ -102,7 +149,17 @@ impl TopBar {
         Self {
             height: 45.0 + 30.0,
             loop_tick_height: 45.0 + 10.0,
-            ..Default::default()
+            loop_button: Button::new(Element::RepeatButton),
+            back_button: Button::new(Element::BackButton),
+            play_button: Button::new(Element::PlayButton),
+            drag: Element::None,
+            hovered: Element::None,
+            loop_start: Duration::ZERO,
+            loop_end: Duration::ZERO,
+            loop_active: false,
+            animation: 0.0,
+            loop_start_tick: Bbox::default(),
+            loop_end_tick: Bbox::default(),
         }
     }
 
@@ -112,9 +169,9 @@ impl TopBar {
 
     fn hovered(&self, x: f32, y: f32) -> Element {
         [
-            (&self.loop_button, Element::RepeatButton),
-            (&self.back_button, Element::BackButton),
-            (&self.play_button, Element::PlayButton),
+            self.loop_button.bbox_with_type(),
+            self.back_button.bbox_with_type(),
+            self.play_button.bbox_with_type(),
             (&self.loop_start_tick, Element::StartTick),
             (&self.loop_end_tick, Element::EndTick),
         ]
@@ -245,7 +302,7 @@ impl TopBar {
         let w = window_state.logical_size.width;
         let progress_x = w * player.percentage();
 
-        let mut is_hovered = window_state.cursor_logical_position.y < h * 1.2;
+        let mut is_hovered = window_state.cursor_logical_position.y < h * 1.7;
 
         if let RewindController::Mouse { .. } = rewind_controller {
             is_hovered = true;
@@ -261,7 +318,7 @@ impl TopBar {
         top_bar.animation = top_bar.animation.max(0.0);
 
         if !is_hovered {
-            fg_quad_pipeline.instances().push(QuadInstance {
+            fg_quad_pipeline.push(QuadInstance {
                 position: [0.0, 0.0],
                 size: [progress_x, 5.0],
                 color: BLUE.into_linear_rgba(),
@@ -281,7 +338,7 @@ impl TopBar {
 
         let y = -h + (bar_animation * h);
 
-        fg_quad_pipeline.instances().push(QuadInstance {
+        fg_quad_pipeline.push(QuadInstance {
             position: [0.0, y],
             size: [w, h],
             color: BAR_BG.into_linear_rgba(),
@@ -289,7 +346,7 @@ impl TopBar {
         });
 
         let progress_x = w * player.percentage();
-        fg_quad_pipeline.instances().push(QuadInstance {
+        fg_quad_pipeline.push(QuadInstance {
             position: [0.0, y + 30.0],
             size: [progress_x, h - 30.0],
             color: BLUE.into_linear_rgba(),
@@ -309,7 +366,7 @@ impl TopBar {
                 DARK_MEASURE
             };
 
-            fg_quad_pipeline.instances().push(QuadInstance {
+            fg_quad_pipeline.push(QuadInstance {
                 position: [x, y + 30.0],
                 size: [1.0, h - 30.0],
                 color: color.into_linear_rgba(),
@@ -317,124 +374,77 @@ impl TopBar {
             });
         }
 
-        update_loop_button(scene, y, w, text);
+        update_buttons(scene, y, w, text);
         update_looper(scene, w, bar_animation);
     }
 }
 
-fn update_loop_button(scene: &mut PlayingScene, y: f32, w: f32, text: &mut TextRenderer) {
-    let top_bar = &mut scene.top_bar;
-
-    top_bar.loop_button = ElementInfo {
-        x: w - 30.0,
-        y,
-        w: 30.0,
-        h: 30.0,
-    };
-
-    let color = if let Element::RepeatButton = top_bar.hovered {
-        BUTTON_HOVER
-    } else {
-        BAR_BG
-    };
-
-    scene.fg_quad_pipeline.instances().push(QuadInstance {
-        position: top_bar.loop_button.pos(),
-        size: top_bar.loop_button.size(),
-        color: color.into_linear_rgba(),
-        border_radius: [5.0; 4],
-    });
-
-    let icon_size = 20.0;
-    text.queue_icon(
-        top_bar.loop_button.x + (top_bar.loop_button.w - icon_size) / 2.0,
-        top_bar.loop_button.y + (top_bar.loop_button.h - icon_size) / 2.0,
-        icon_size,
-        repeat_icon(),
-    );
-
-    {
-        top_bar.play_button = ElementInfo {
-            x: top_bar.loop_button.x - 30.0,
-            ..top_bar.loop_button
-        };
-
-        let color = if let Element::PlayButton = top_bar.hovered {
-            BUTTON_HOVER
-        } else {
-            BAR_BG
-        };
-        scene.fg_quad_pipeline.instances().push(QuadInstance {
-            position: top_bar.play_button.pos(),
-            size: top_bar.play_button.size(),
-            color: color.into_linear_rgba(),
-            border_radius: [5.0; 4],
-        });
-        text.queue_icon(
-            top_bar.play_button.x + (top_bar.play_button.w - icon_size) / 2.0,
-            top_bar.play_button.y + (top_bar.play_button.h - icon_size) / 2.0,
-            icon_size,
-            if scene.player.is_paused() {
-                play_icon()
-            } else {
-                pause_icon()
-            },
-        );
-    }
-
-    {
-        top_bar.back_button = ElementInfo {
-            x: 0.0,
-            ..top_bar.loop_button
-        };
-
-        let color = if let Element::BackButton = top_bar.hovered {
-            BUTTON_HOVER
+fn update_buttons(scene: &mut PlayingScene, y: f32, w: f32, text: &mut TextRenderer) {
+    let PlayingScene {
+        top_bar,
+        fg_quad_pipeline,
+        ..
+    } = scene;
+
+    top_bar
+        .back_button
+        .set_x(0.0)
+        .set_y(y)
+        .set_hovered(matches!(top_bar.hovered, Element::BackButton))
+        .set_icon(left_arrow_icon())
+        .draw(fg_quad_pipeline, text);
+
+    top_bar
+        .loop_button
+        .set_x(w - 30.0)
+        .set_y(y)
+        .set_hovered(matches!(top_bar.hovered, Element::RepeatButton))
+        .set_icon(repeat_icon())
+        .draw(fg_quad_pipeline, text);
+
+    top_bar
+        .play_button
+        .set_x(w - 30.0 * 2.0)
+        .set_y(y)
+        .set_hovered(matches!(top_bar.hovered, Element::PlayButton))
+        .set_icon(if scene.player.is_paused() {
+            play_icon()
         } else {
-            BAR_BG
-        };
-        scene.fg_quad_pipeline.instances().push(QuadInstance {
-            position: top_bar.back_button.pos(),
-            size: top_bar.back_button.size(),
-            color: color.into_linear_rgba(),
-            border_radius: [5.0; 4],
-        });
-
-        text.queue_icon(
-            top_bar.back_button.x + (top_bar.back_button.w - icon_size) / 2.0,
-            top_bar.back_button.y + (top_bar.back_button.h - icon_size) / 2.0,
-            icon_size,
-            left_arrow_icon(),
-        );
-    }
+            pause_icon()
+        })
+        .draw(fg_quad_pipeline, text);
 }
 
 fn update_looper(scene: &mut PlayingScene, w: f32, animation: f32) {
-    let top_bar = &mut scene.top_bar;
-    let quad_pipeline = &mut scene.fg_quad_pipeline;
-
-    let h = top_bar.loop_tick_height;
-
-    top_bar.loop_start_tick = ElementInfo {
-        x: scene.player.time_to_percentage(&top_bar.loop_start) * w,
-        y: 30.0,
-        w: 5.0,
-        h,
-    };
-    top_bar.loop_end_tick = ElementInfo {
-        x: scene.player.time_to_percentage(&top_bar.loop_end) * w,
-        y: 30.0,
-        w: 5.0,
-        h,
-    };
+    let PlayingScene {
+        top_bar,
+        fg_quad_pipeline,
+        ref player,
+        ..
+    } = scene;
 
     if !top_bar.loop_active {
         return;
     }
 
-    let offset = top_bar.loop_start_tick.y + h;
-    top_bar.loop_start_tick.y += -offset + (animation * offset);
-    top_bar.loop_end_tick.y = top_bar.loop_start_tick.y;
+    let h = top_bar.loop_tick_height;
+    let offset = 30.0 + h;
+    let y = 30.0 - offset + (animation * offset);
+
+    top_bar.loop_start_tick = Bbox::new(
+        Point {
+            x: player.time_to_percentage(&top_bar.loop_start) * w,
+            y,
+        },
+        Size::new(5.0, h),
+    );
+    top_bar.loop_end_tick = Bbox::new(
+        Point {
+            x: player.time_to_percentage(&top_bar.loop_end) * w,
+            y,
+        },
+        Size::new(5.0, h),
+    );
 
     let (start_color, end_color) = match (top_bar.hovered, top_bar.drag) {
         (Element::StartTick, _) | (_, Element::StartTick) => (WHITE, LOOPER),
@@ -444,25 +454,21 @@ fn update_looper(scene: &mut PlayingScene, w: f32, animation: f32) {
 
     let color = Color { a: 0.35, ..LOOPER };
 
-    let length = top_bar.loop_end_tick.x - top_bar.loop_start_tick.x;
+    let length = top_bar.loop_end_tick.x() - top_bar.loop_start_tick.x();
 
-    quad_pipeline.instances().push(QuadInstance {
-        position: top_bar.loop_start_tick.pos(),
-        size: [length, top_bar.loop_start_tick.h],
-        color: color.into_linear_rgba(),
-        ..Default::default()
-    });
+    let mut bg_box = top_bar.loop_start_tick;
+    bg_box.size.w = length;
 
-    quad_pipeline.instances().push(QuadInstance {
-        position: top_bar.loop_start_tick.pos(),
-        size: top_bar.loop_start_tick.size(),
-        color: start_color.into_linear_rgba(),
-        ..Default::default()
-    });
-    quad_pipeline.instances().push(QuadInstance {
-        position: top_bar.loop_end_tick.pos(),
-        size: top_bar.loop_end_tick.size(),
-        color: end_color.into_linear_rgba(),
+    draw_rect(fg_quad_pipeline, &bg_box, &color);
+    draw_rect(fg_quad_pipeline, &top_bar.loop_start_tick, &start_color);
+    draw_rect(fg_quad_pipeline, &top_bar.loop_end_tick, &end_color);
+}
+
+fn draw_rect(quad_pipeline: &mut QuadPipeline, bbox: &Bbox, color: &Color) {
+    quad_pipeline.push(QuadInstance {
+        position: bbox.pos.into(),
+        size: bbox.size.into(),
+        color: color.into_linear_rgba(),
         ..Default::default()
     });
 }