mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
refactoring to separate UI folder for future UI framework
This commit is contained in:
parent
9dda23cf8b
commit
d289dc9f73
34 changed files with 900 additions and 2962 deletions
|
@ -2,471 +2,19 @@
|
|||
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
// Inspired by:
|
||||
// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, licensed under the MIT license
|
||||
// https://github.com/cloudhead/rgx by Alexis Sellier, licensed under the MIT license
|
||||
|
||||
// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/
|
||||
|
||||
extern crate pest;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate pest_derive;
|
||||
|
||||
use crate::error::{print_err, EdResult};
|
||||
use crate::graphics::colors::{CODE_COLOR, TXT_COLOR};
|
||||
use crate::graphics::lowlevel::buffer::create_rect_buffers;
|
||||
use crate::graphics::lowlevel::ortho::update_ortho_buffer;
|
||||
use crate::graphics::lowlevel::pipelines;
|
||||
use crate::graphics::primitives::text::{
|
||||
build_glyph_brush, example_code_glyph_rect, queue_code_text_draw, queue_text_draw, Text,
|
||||
};
|
||||
use crate::graphics::style::CODE_FONT_SIZE;
|
||||
use crate::graphics::style::CODE_TXT_XY;
|
||||
use crate::mvc::app_model::AppModel;
|
||||
use crate::mvc::ed_model::EdModel;
|
||||
use crate::mvc::{app_update, ed_model, ed_view};
|
||||
//use crate::resources::strings::NOTHING_OPENED;
|
||||
use crate::vec_result::get_res;
|
||||
use bumpalo::Bump;
|
||||
use cgmath::Vector2;
|
||||
use ed_model::Position;
|
||||
use lang::{pool::Pool, scope::Scope};
|
||||
use pipelines::RectResources;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::symbol::{IdentIds, ModuleIds};
|
||||
use roc_region::all::Region;
|
||||
use roc_types::subs::VarStore;
|
||||
use std::error::Error;
|
||||
mod graphics;
|
||||
mod lang;
|
||||
mod editor;
|
||||
mod ui;
|
||||
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use wgpu::{CommandEncoder, RenderPass, TextureView};
|
||||
use wgpu_glyph::GlyphBrush;
|
||||
use winit::dpi::PhysicalSize;
|
||||
use winit::event;
|
||||
use winit::event::{Event, ModifiersState};
|
||||
use winit::event_loop::ControlFlow;
|
||||
|
||||
pub mod error;
|
||||
pub mod graphics;
|
||||
mod keyboard_input;
|
||||
pub mod lang;
|
||||
mod mvc;
|
||||
//pub mod mvc; // for benchmarking
|
||||
mod ui;
|
||||
mod resources;
|
||||
mod selection;
|
||||
mod text_buffer;
|
||||
//pub mod text_buffer; // for benchmarking
|
||||
mod render;
|
||||
mod util;
|
||||
mod vec_result;
|
||||
|
||||
/// The editor is actually launched from the CLI if you pass it zero arguments,
|
||||
/// or if you provide it 1 or more files or directories to open on launch.
|
||||
pub fn launch(filepaths: &[&Path]) -> io::Result<()> {
|
||||
//TODO support using multiple filepaths
|
||||
let first_path_opt = if !filepaths.is_empty() {
|
||||
match get_res(0, filepaths) {
|
||||
Ok(path_ref_ref) => Some(*path_ref_ref),
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
run_event_loop(first_path_opt).expect("Error running event loop");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
||||
env_logger::init();
|
||||
|
||||
// Open window and create a surface
|
||||
let event_loop = winit::event_loop::EventLoop::new();
|
||||
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_inner_size(PhysicalSize::new(1200.0, 1000.0))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::BackendBit::all());
|
||||
|
||||
let surface = unsafe { instance.create_surface(&window) };
|
||||
|
||||
// Initialize GPU
|
||||
let (gpu_device, cmd_queue) = futures::executor::block_on(async {
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
compatible_surface: Some(&surface),
|
||||
})
|
||||
.await
|
||||
.expect("Request adapter");
|
||||
|
||||
adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
features: wgpu::Features::empty(),
|
||||
limits: wgpu::Limits::default(),
|
||||
shader_validation: false,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Request device")
|
||||
});
|
||||
|
||||
// Create staging belt and a local pool
|
||||
let mut staging_belt = wgpu::util::StagingBelt::new(1024);
|
||||
let mut local_pool = futures::executor::LocalPool::new();
|
||||
let local_spawner = local_pool.spawner();
|
||||
|
||||
// Prepare swap chain
|
||||
let render_format = wgpu::TextureFormat::Bgra8Unorm;
|
||||
let mut size = window.inner_size();
|
||||
|
||||
let swap_chain_descr = wgpu::SwapChainDescriptor {
|
||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
||||
format: render_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
//Immediate may cause tearing, change present_mode if this becomes a problem
|
||||
present_mode: wgpu::PresentMode::Immediate,
|
||||
};
|
||||
|
||||
let mut swap_chain = gpu_device.create_swap_chain(&surface, &swap_chain_descr);
|
||||
|
||||
let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &swap_chain_descr);
|
||||
|
||||
let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?;
|
||||
|
||||
let is_animating = true;
|
||||
let ed_model_opt = if let Some(file_path) = file_path_opt {
|
||||
let ed_model_res = ed_model::init_model(file_path);
|
||||
|
||||
match ed_model_res {
|
||||
Ok(mut ed_model) => {
|
||||
ed_model.glyph_dim_rect_opt = Some(example_code_glyph_rect(&mut glyph_brush));
|
||||
|
||||
Some(ed_model)
|
||||
}
|
||||
Err(e) => {
|
||||
print_err(&e);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut app_model = AppModel::init(ed_model_opt);
|
||||
|
||||
let mut keyboard_modifiers = ModifiersState::empty();
|
||||
|
||||
// This arena is never cleared and should only be used for allocations that occur rarely
|
||||
let arena = Bump::new();
|
||||
|
||||
let mut rects_arena = Bump::new();
|
||||
|
||||
// Render loop
|
||||
window.request_redraw();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
// TODO dynamically switch this on/off depending on whether any
|
||||
// animations are running. Should conserve CPU usage and battery life!
|
||||
if is_animating {
|
||||
*control_flow = ControlFlow::Poll;
|
||||
} else {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
}
|
||||
|
||||
match event {
|
||||
//Close
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
//Resize
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::Resized(new_size),
|
||||
..
|
||||
} => {
|
||||
size = new_size;
|
||||
|
||||
swap_chain = gpu_device.create_swap_chain(
|
||||
&surface,
|
||||
&wgpu::SwapChainDescriptor {
|
||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
||||
format: render_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
//Immediate may cause tearing, change present_mode if this becomes a problem
|
||||
present_mode: wgpu::PresentMode::Immediate,
|
||||
},
|
||||
);
|
||||
|
||||
update_ortho_buffer(
|
||||
size.width,
|
||||
size.height,
|
||||
&gpu_device,
|
||||
&rect_resources.ortho.buffer,
|
||||
&cmd_queue,
|
||||
);
|
||||
}
|
||||
//Received Character
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::ReceivedCharacter(ch),
|
||||
..
|
||||
} => {
|
||||
if let Err(e) = app_update::handle_new_char(&ch, &mut app_model) {
|
||||
print_err(&e)
|
||||
}
|
||||
}
|
||||
//Keyboard Input
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::KeyboardInput { input, .. },
|
||||
..
|
||||
} => {
|
||||
if let Some(virtual_keycode) = input.virtual_keycode {
|
||||
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||
if ed_model.has_focus {
|
||||
let keydown_res = keyboard_input::handle_keydown(
|
||||
input.state,
|
||||
virtual_keycode,
|
||||
keyboard_modifiers,
|
||||
&mut app_model,
|
||||
);
|
||||
|
||||
if let Err(e) = keydown_res {
|
||||
print_err(&e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//Modifiers Changed
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::ModifiersChanged(modifiers),
|
||||
..
|
||||
} => {
|
||||
keyboard_modifiers = modifiers;
|
||||
}
|
||||
Event::MainEventsCleared => window.request_redraw(),
|
||||
Event::RedrawRequested { .. } => {
|
||||
// Get a command encoder for the current frame
|
||||
let mut encoder =
|
||||
gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Redraw"),
|
||||
});
|
||||
|
||||
let frame = swap_chain
|
||||
.get_current_frame()
|
||||
.expect("Failed to acquire next SwapChainFrame")
|
||||
.output;
|
||||
|
||||
if let Some(ed_model) = &app_model.ed_model_opt {
|
||||
//TODO don't pass invisible lines
|
||||
queue_editor_text(
|
||||
&size,
|
||||
&ed_model.text_buf.all_lines(&arena),
|
||||
ed_model.caret_pos,
|
||||
CODE_TXT_XY.into(),
|
||||
&mut glyph_brush,
|
||||
);
|
||||
} else {
|
||||
// queue_no_file_text(&size, NOTHING_OPENED, CODE_TXT_XY.into(), &mut glyph_brush);
|
||||
|
||||
let mut pool = Pool::with_capacity(12);
|
||||
let mut var_store = VarStore::default();
|
||||
let dep_idents = MutMap::default();
|
||||
let mut module_ids = ModuleIds::default();
|
||||
let exposed_ident_ids = IdentIds::default();
|
||||
|
||||
let home = module_ids.get_or_insert(&"Home".into());
|
||||
|
||||
let mut env = crate::lang::expr::Env::new(
|
||||
home,
|
||||
&arena,
|
||||
&mut pool,
|
||||
&mut var_store,
|
||||
dep_idents,
|
||||
&module_ids,
|
||||
exposed_ident_ids,
|
||||
);
|
||||
|
||||
let mut scope = Scope::new(home, env.pool, env.var_store);
|
||||
|
||||
let region = Region::new(0, 0, 0, 0);
|
||||
|
||||
let (expr2, _) = crate::lang::expr::str_to_expr2(
|
||||
&arena, "True", &mut env, &mut scope, region,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
render::render_expr2(
|
||||
&mut env,
|
||||
&size,
|
||||
&expr2,
|
||||
CODE_TXT_XY.into(),
|
||||
&mut glyph_brush,
|
||||
);
|
||||
}
|
||||
|
||||
rects_arena.reset();
|
||||
|
||||
match draw_all_rects(
|
||||
&app_model.ed_model_opt,
|
||||
&rects_arena,
|
||||
&mut encoder,
|
||||
&frame.view,
|
||||
&gpu_device,
|
||||
&rect_resources,
|
||||
) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
print_err(&e);
|
||||
begin_render_pass(&mut encoder, &frame.view);
|
||||
}
|
||||
}
|
||||
|
||||
// draw all text
|
||||
glyph_brush
|
||||
.draw_queued(
|
||||
&gpu_device,
|
||||
&mut staging_belt,
|
||||
&mut encoder,
|
||||
&frame.view,
|
||||
size.width,
|
||||
size.height,
|
||||
)
|
||||
.expect("Draw queued");
|
||||
|
||||
staging_belt.finish();
|
||||
cmd_queue.submit(Some(encoder.finish()));
|
||||
|
||||
// Recall unused staging buffers
|
||||
use futures::task::SpawnExt;
|
||||
|
||||
local_spawner
|
||||
.spawn(staging_belt.recall())
|
||||
.expect("Recall staging belt");
|
||||
|
||||
local_pool.run_until_stalled();
|
||||
}
|
||||
_ => {
|
||||
*control_flow = winit::event_loop::ControlFlow::Wait;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn draw_all_rects(
|
||||
ed_model_opt: &Option<EdModel>,
|
||||
arena: &Bump,
|
||||
encoder: &mut CommandEncoder,
|
||||
texture_view: &TextureView,
|
||||
gpu_device: &wgpu::Device,
|
||||
rect_resources: &RectResources,
|
||||
) -> EdResult<()> {
|
||||
if let Some(ed_model) = ed_model_opt {
|
||||
let all_rects = ed_view::create_ed_rects(ed_model, arena)?;
|
||||
|
||||
let rect_buffers = create_rect_buffers(gpu_device, encoder, &all_rects);
|
||||
|
||||
let mut render_pass = begin_render_pass(encoder, texture_view);
|
||||
|
||||
render_pass.set_pipeline(&rect_resources.pipeline);
|
||||
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
|
||||
render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..));
|
||||
render_pass.set_index_buffer(rect_buffers.index_buffer.slice(..));
|
||||
render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1);
|
||||
} else {
|
||||
// need to begin render pass to clear screen
|
||||
begin_render_pass(encoder, texture_view);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn begin_render_pass<'a>(
|
||||
encoder: &'a mut CommandEncoder,
|
||||
texture_view: &'a TextureView,
|
||||
) -> RenderPass<'a> {
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
|
||||
attachment: texture_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.1,
|
||||
g: 0.2,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
}],
|
||||
depth_stencil_attachment: None,
|
||||
})
|
||||
}
|
||||
|
||||
// returns bounding boxes for every glyph
|
||||
fn queue_editor_text(
|
||||
size: &PhysicalSize<u32>,
|
||||
editor_lines: &str,
|
||||
caret_pos: Position,
|
||||
code_coords: Vector2<f32>,
|
||||
glyph_brush: &mut GlyphBrush<()>,
|
||||
) {
|
||||
let area_bounds = (size.width as f32, size.height as f32).into();
|
||||
|
||||
let code_text = Text {
|
||||
position: code_coords,
|
||||
area_bounds,
|
||||
color: CODE_COLOR.into(),
|
||||
text: editor_lines,
|
||||
size: CODE_FONT_SIZE,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let s = format!("Ln {}, Col {}", caret_pos.line, caret_pos.column);
|
||||
let text = s.as_str();
|
||||
|
||||
let caret_pos_label = Text {
|
||||
position: ((size.width as f32) - 150.0, (size.height as f32) - 40.0).into(),
|
||||
area_bounds,
|
||||
color: TXT_COLOR.into(),
|
||||
text,
|
||||
size: 25.0,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
queue_text_draw(&caret_pos_label, glyph_brush);
|
||||
|
||||
queue_code_text_draw(&code_text, glyph_brush);
|
||||
}
|
||||
|
||||
fn _queue_no_file_text(
|
||||
size: &PhysicalSize<u32>,
|
||||
text: &str,
|
||||
text_coords: Vector2<f32>,
|
||||
glyph_brush: &mut GlyphBrush<()>,
|
||||
) {
|
||||
let area_bounds = (size.width as f32, size.height as f32).into();
|
||||
|
||||
let code_text = Text {
|
||||
position: text_coords,
|
||||
area_bounds,
|
||||
color: CODE_COLOR.into(),
|
||||
text,
|
||||
size: CODE_FONT_SIZE,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
queue_text_draw(&code_text, glyph_brush);
|
||||
}
|
||||
editor::main::launch(filepaths)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue