diff --git a/Cargo.lock b/Cargo.lock
index d152631..90ef64d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1299,6 +1299,7 @@ dependencies = [
  "egui",
  "egui-wgpu",
  "env_logger",
+ "epaint",
  "flare-shader",
  "futures",
  "futures-executor",
diff --git a/crates/flare-shader/src/lib.rs b/crates/flare-shader/src/lib.rs
index 52ee960..5cd80a6 100644
--- a/crates/flare-shader/src/lib.rs
+++ b/crates/flare-shader/src/lib.rs
@@ -158,15 +158,24 @@ impl ThreadState {
 #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
 #[repr(C)]
 pub struct Coefs {
-    a: f32,
-    b: f32,
-    c: f32,
-    d: f32,
-    e: f32,
-    f: f32,
+    pub a: f32,
+    pub b: f32,
+    pub c: f32,
+    pub d: f32,
+    pub e: f32,
+    pub f: f32,
 }
 
 impl Coefs {
+    pub const IDENTITY: Coefs = Coefs {
+        a: 1.0,
+        b: 0.0,
+        c: 0.0,
+        d: 0.0,
+        e: 1.0,
+        f: 0.0
+    };
+
     pub fn new(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self {
         Self { a, b, c, d, e, f }
     }
diff --git a/crates/flare/Cargo.toml b/crates/flare/Cargo.toml
index 75d8d4a..20f0a20 100644
--- a/crates/flare/Cargo.toml
+++ b/crates/flare/Cargo.toml
@@ -12,6 +12,7 @@ eframe = { version = "0.31", features = ["wgpu"]}
 egui = "0.31"
 egui-wgpu = "0.31"
 env_logger.workspace = true
+epaint = "0.31"
 flare-shader = { path = "../flare-shader" }
 futures = "0.3"
 futures-executor.workspace = true
diff --git a/crates/flare/examples/transform_editor.rs b/crates/flare/examples/transform_editor.rs
new file mode 100644
index 0000000..65f8da9
--- /dev/null
+++ b/crates/flare/examples/transform_editor.rs
@@ -0,0 +1,60 @@
+use flare::transform_editor::TransformEditor;
+use flare_shader::Coefs;
+
+struct TransformEditorApp {
+    transform_editor: TransformEditor,
+}
+
+impl TransformEditorApp {
+    fn new() -> Self {
+        Self {
+            transform_editor: TransformEditor::new()
+        }
+    }
+}
+
+impl eframe::App for TransformEditorApp {
+    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
+        egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
+            egui::menu::bar(ui, |ui| {
+                ui.menu_button("File", |ui| {
+                    if ui.button("Quit").clicked() {
+                        ctx.send_viewport_cmd(egui::ViewportCommand::Close);
+                    }
+                });
+                if ui.button("Add Transform").clicked() {
+                    self.transform_editor.add_transform(Coefs::IDENTITY)
+                }
+            })
+        });
+
+        egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| {
+            ui.label(format!("Transforms: {}", self.transform_editor.len()))
+        });
+
+        egui::CentralPanel::default().show(ctx, |ui| {
+            egui::Frame::canvas(ui.style()).show(ui, |ui| {
+                self.transform_editor.ui(ui)
+            })
+        });
+    }
+}
+
+fn main() -> eframe::Result {
+    env_logger::init();
+
+    let initial_dimensions = egui::vec2(800., 600.);
+    let native_options = eframe::NativeOptions {
+        viewport: egui::ViewportBuilder::default().with_inner_size(initial_dimensions),
+        renderer: eframe::Renderer::Wgpu,
+        ..Default::default()
+    };
+
+    eframe::run_native(
+        "transform_editor",
+        native_options,
+        Box::new(|cc| {
+            Ok(Box::new(TransformEditorApp::new()))
+        }),
+    )
+}
\ No newline at end of file
diff --git a/crates/flare/src/lib.rs b/crates/flare/src/lib.rs
new file mode 100644
index 0000000..dfb0b30
--- /dev/null
+++ b/crates/flare/src/lib.rs
@@ -0,0 +1 @@
+pub mod transform_editor;
\ No newline at end of file
diff --git a/crates/flare/src/main.rs b/crates/flare/src/main.rs
index f47b7b6..dd04c62 100644
--- a/crates/flare/src/main.rs
+++ b/crates/flare/src/main.rs
@@ -15,6 +15,8 @@ use std::fs::File;
 use wgpu::util::DeviceExt;
 use wgpu::{CommandBuffer, CommandEncoder, Device, Queue, RenderPass};
 
+pub mod transform_editor;
+
 struct RenderGroup {
     device: wgpu::Device,
     queue: wgpu::Queue,
diff --git a/crates/flare/src/transform_editor.rs b/crates/flare/src/transform_editor.rs
new file mode 100644
index 0000000..bad915c
--- /dev/null
+++ b/crates/flare/src/transform_editor.rs
@@ -0,0 +1,57 @@
+use egui::{emath, Rect, Sense};
+use epaint::{Color32, Shape, Stroke};
+use flare_shader::Coefs;
+
+pub struct TransformEditor {
+    transforms: Vec<Coefs>
+}
+
+fn coef_to_shapes(coef: &Coefs, to_screen: emath::RectTransform) -> Vec<Shape> {
+    let origin = to_screen.transform_pos(egui::pos2(coef.c, -coef.f));
+    let x = to_screen.transform_pos(egui::pos2(coef.c + coef.a, -(coef.f + coef.b)));
+    let y = to_screen.transform_pos(egui::pos2(coef.c + coef.d, -(coef.f + coef.e)));
+
+    let stroke = Stroke::new(2.0, Color32::BLUE);
+    let translucent = Color32::from_rgba_unmultiplied(0, 0, 255, 8);
+
+    let shapes = vec![
+        Shape::circle_stroke(origin, 5.0, stroke),
+        Shape::circle_stroke(x, 5.0, stroke),
+        Shape::circle_stroke(y, 5.0, stroke),
+        Shape::convex_polygon(vec![origin, x, y], translucent, stroke),
+    ];
+
+    shapes
+}
+
+impl TransformEditor {
+    pub fn new() -> Self {
+        let mut editor = Self { transforms: vec![] };
+        editor.add_transform(Coefs::IDENTITY);
+
+        editor
+    }
+
+    pub fn add_transform(&mut self, coefs: Coefs) {
+        self.transforms.push(coefs);
+    }
+
+    pub fn len(&self) -> usize {
+        self.transforms.len()
+    }
+
+    pub fn ui(&mut self, ui: &mut egui::Ui) -> egui::Response {
+        let (response, painter) = ui.allocate_painter(ui.available_size(), Sense::hover());
+
+        let transform_area = Rect::from_min_max(egui::pos2(-2.0, -2.0), egui::pos2(2.0, 2.0));
+        let to_screen = emath::RectTransform::from_to(transform_area, response.interact_rect);
+
+        self.transforms.iter().map(|coef| {
+            coef_to_shapes(coef, to_screen)
+        }).flat_map(|x| x).for_each(|shape| {
+            let _ = painter.add(shape);
+        });
+
+        response
+    }
+}
\ No newline at end of file