This commit is contained in:
TriMill 2023-05-30 21:46:04 -04:00
parent ee26b6133a
commit 3e9f05bbfd
11 changed files with 222 additions and 174 deletions

View file

@ -16,24 +16,25 @@
<textarea id="srcinput"></textarea> <textarea id="srcinput"></textarea>
</div> </div>
<div id="canvas_container" class="split right"> <div id="canvas_container" class="split right">
<!-- canvas -->
</div> </div>
</div> </div>
</div> </div>
<script type="module"> <script type="module">
import init, * as cxgraph from "./pkg/cxgraph.js"; import init, * as cxgraph from "./pkg/cxgraph.js";
await init(); await init();
await cxgraph.load_shader("plot(z) = z");
await cxgraph.redraw();
let canvas = document.getElementsByTagName("canvas")[0];
canvas.style.width = "100%"; canvas.style.width = "100%";
canvas.style.height = "100%"; canvas.style.height = "100%";
await cxgraph.load_shader("f(z) = z^8 + 15z^4 - 16\nfp(z) = 8z^7 + 60z^3\nn(z) = z - f(z)/fp(z)\nni: iter n, 60\nplot(z) = f(z)");
await cxgraph.redraw();
let canvas = document.getElementsByTagName("canvas")[0];
new ResizeObserver(async () => { new ResizeObserver(async () => {
//console.log("resize"); let width = canvas.clientWidth;
//let width = canvas.clientWidth; let height = canvas.clientHeight;
//let height = canvas.clientHeight; await cxgraph.resize(width*2, height*2);
//await cxgraph.resize(width, height); await cxgraph.set_bounds(-5*width/height, -5, 5*width/height, 5);
//await cxgraph.redraw(); await cxgraph.redraw();
}).observe(canvas); }).observe(canvas);
</script> </script>
</body> </body>

View file

@ -2,7 +2,7 @@ use std::{collections::{HashMap, HashSet}, fmt::Write};
use crate::complex::{Complex, cxfn}; use crate::complex::{Complex, cxfn};
use super::parser::{Expr, Stmt, UnaryOp, BinaryOp}; use super::parser::{Expr, Stmt, UnaryOp, BinaryOp, Defn};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum CompileError<'s> { pub enum CompileError<'s> {
@ -85,10 +85,10 @@ type LocalTable<'s> = HashSet<&'s str>;
type CompileResult<'s,T=()> = Result<T, CompileError<'s>>; type CompileResult<'s,T=()> = Result<T, CompileError<'s>>;
pub fn compile<'s>(buf: &mut impl Write, stmts: &[Stmt<'s>]) -> CompileResult<'s> { pub fn compile<'s>(buf: &mut impl Write, defns: &[Defn<'s>]) -> CompileResult<'s> {
let mut compiler = Compiler::new(buf); let mut compiler = Compiler::new(buf);
for stmt in stmts { for defn in defns {
compiler.compile_stmt(stmt)?; compiler.compile_defn(defn)?;
} }
Ok(()) Ok(())
} }
@ -110,18 +110,16 @@ impl <'s, 'w, W: Write> Compiler<'s, 'w, W> {
// Statements // // Statements //
////////////////// //////////////////
fn compile_stmt(&mut self, stmt: &Stmt<'s>) -> CompileResult<'s> { fn compile_defn(&mut self, defn: &Defn<'s>) -> CompileResult<'s> {
let res = match stmt { let res = match defn {
Stmt::Const { name, body } => self.stmt_const(name, body), Defn::Const { name, body } => self.defn_const(name, body),
Stmt::Func { name, args, body } => self.stmt_func(name, args, body), Defn::Func { name, args, body } => self.defn_func(name, args, body),
Stmt::Deriv { name, func } => self.stmt_deriv(name, func),
Stmt::Iter { name, func, count } => self.stmt_iter(name, func, *count),
}; };
writeln!(self.buf)?; writeln!(self.buf)?;
res res
} }
fn stmt_const(&mut self, name: &'s str, body: &Expr<'s>) -> CompileResult<'s> { fn defn_const(&mut self, name: &'s str, body: &Expr<'s>) -> CompileResult<'s> {
if self.name_info(name, None).is_some() { if self.name_info(name, None).is_some() {
return Err(CompileError::Reassignment(name)) return Err(CompileError::Reassignment(name))
} }
@ -136,7 +134,7 @@ impl <'s, 'w, W: Write> Compiler<'s, 'w, W> {
Ok(()) Ok(())
} }
fn stmt_func(&mut self, name: &'s str, args: &[&'s str], body: &Expr<'s>) -> CompileResult<'s> { fn defn_func(&mut self, name: &'s str, args: &[&'s str], body: &(Vec<Stmt<'s>>, Expr<'s>)) -> CompileResult<'s> {
if self.name_info(name, None).is_some() { if self.name_info(name, None).is_some() {
return Err(CompileError::Reassignment(name)) return Err(CompileError::Reassignment(name))
} }
@ -150,7 +148,7 @@ impl <'s, 'w, W: Write> Compiler<'s, 'w, W> {
locals.insert(arg); locals.insert(arg);
} }
write!(self.buf, ")->vec2f{{return ")?; write!(self.buf, ")->vec2f{{return ")?;
self.compile_expr(&locals, body)?; self.compile_expr(&locals, &body.1)?;
write!(self.buf, ";}}")?; write!(self.buf, ";}}")?;
Ok(()) Ok(())
} }

View file

@ -17,7 +17,7 @@ pub struct Position {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Error<'s> { pub enum Error<'s> {
Parse(ParseError<'s>), Parse(ParseError),
Compile(CompileError<'s>), Compile(CompileError<'s>),
} }
@ -27,16 +27,19 @@ impl <'s> From<CompileError<'s>> for Error<'s> {
} }
} }
impl <'s> From<ParseError<'s>> for Error<'s> { impl <'s> From<ParseError> for Error<'s> {
fn from(value: ParseError<'s>) -> Self { fn from(value: ParseError) -> Self {
Self::Parse(value) Self::Parse(value)
} }
} }
pub fn compile(src: &str) -> Result<String, Error> { pub fn compile(src: &str) -> Result<String, Error> {
let mut buf = String::new(); let mut buf = String::new();
let stmts = parser::Parser::new(src).parse()?; println!("parsing");
let stmts = optimize(stmts); let defns = parser::Parser::new(src).parse()?;
compiler::compile(&mut buf, &stmts)?; println!("optimizing");
let defns = optimize(defns);
println!("compiling");
compiler::compile(&mut buf, &defns)?;
Ok(buf) Ok(buf)
} }

View file

@ -1,6 +1,6 @@
use crate::complex::{Complex, cxfn}; use crate::complex::{Complex, cxfn};
use super::{parser::{Expr, UnaryOp, BinaryOp, Stmt}, compiler::{BUILTINS, Type}}; use super::{parser::{Expr, UnaryOp, BinaryOp, Stmt, Defn}, compiler::{BUILTINS, Type}};
fn apply_unary(op: UnaryOp, arg: Complex) -> Complex { fn apply_unary(op: UnaryOp, arg: Complex) -> Complex {
match op { match op {
@ -20,14 +20,29 @@ fn apply_binary(op: BinaryOp, u: Complex, v: Complex) -> Complex {
} }
} }
pub fn optimize(stmts: Vec<Stmt>) -> Vec<Stmt> { pub fn optimize(defns: Vec<Defn>) -> Vec<Defn> {
stmts.into_iter().map(|s| match s { defns.into_iter().map(|s| match s {
Stmt::Const { name, body } => Stmt::Const { name, body: optimize_expr(body) }, Defn::Const { name, body } => Defn::Const { name, body: optimize_expr(body) },
Stmt::Func { name, args, body } => Stmt::Func { name, args, body: optimize_expr(body) }, Defn::Func { name, args, body } => {
let stmts = body.0.into_iter()
.map(optimize_stmt).collect();
let expr = optimize_expr(body.1);
Defn::Func { name, args, body: (stmts, expr) }
}
_ => s, _ => s,
}).collect() }).collect()
} }
fn optimize_stmt(stmt: Stmt) -> Stmt {
match stmt {
Stmt::Expr(e) => Stmt::Expr(optimize_expr(e)),
Stmt::Store(v, e) => Stmt::Store(v, optimize_expr(e)),
Stmt::Iter(v, min, max, stmts) => Stmt::Iter(v, min, max,
stmts.into_iter().map(optimize_stmt).collect()
)
}
}
fn optimize_expr<'s>(e: Expr<'s>) -> Expr<'s> { fn optimize_expr<'s>(e: Expr<'s>) -> Expr<'s> {
match e { match e {
Expr::Unary(op, arg) => { Expr::Unary(op, arg) => {

View file

@ -5,14 +5,11 @@ use crate::complex::Complex;
use super::{scanner::{Scanner, Token}, Position}; use super::{scanner::{Scanner, Token}, Position};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum ParseError<'s> { pub struct ParseError {
UnexpectedExpr(Position, Expr<'s>), msg: String,
Unexpected(Position, Token<'s>), pos: Option<Position>,
Expected(Position, Token<'s>),
InvalidLValue(Position),
InvalidFunction(Expr<'s>),
UnexpectedEof,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum BinaryOp { pub enum BinaryOp {
Add, Sub, Mul, Div, Pow, Add, Sub, Mul, Div, Pow,
@ -73,10 +70,14 @@ pub enum Expr<'s> {
#[derive(Debug)] #[derive(Debug)]
pub enum Stmt<'s> { pub enum Stmt<'s> {
Expr(Expr<'s>),
Store(&'s str, Expr<'s>),
Iter(&'s str, u32, u32, Vec<Stmt<'s>>),
}
pub enum Defn<'s> {
Const { name: &'s str, body: Expr<'s> }, Const { name: &'s str, body: Expr<'s> },
Func { name: &'s str, args: Vec<&'s str>, body: Expr<'s> }, Func { name: &'s str, args: Vec<&'s str>, body: (Vec<Stmt<'s>>, Expr<'s>) },
Deriv { name: &'s str, func: &'s str },
Iter { name: &'s str, func: &'s str, count: u32 }
} }
pub struct Parser<'s> { pub struct Parser<'s> {
@ -90,20 +91,46 @@ impl <'s> Parser<'s> {
} }
} }
fn expect(&mut self, tok: Token<'s>) -> Result<Position, ParseError<'s>> { fn next(&mut self) -> Result<(Position, Token<'s>), ParseError> {
match self.scanner.peek() { match self.scanner.next() {
Some((p, t)) if *t == tok => { Some(r) => Ok(r),
let p = *p; None => Err(self.err_here("Unexpected EOF")),
self.scanner.next();
Ok(p)
},
Some((p, _)) => Err(ParseError::Expected(*p, tok)),
None => Err(ParseError::UnexpectedEof),
} }
} }
fn expr(&mut self, min_prec: u32) -> Result<Expr<'s>, ParseError<'s>> { fn peek(&mut self) -> Result<(Position, Token<'s>), ParseError> {
let (pos, tok) = self.scanner.next().unwrap(); match self.scanner.peek() {
Some(r) => Ok(*r),
None => Err(self.err_here("Unexpected EOF")),
}
}
fn at_end(&mut self) -> bool {
self.scanner.peek().is_none()
}
fn expect(&mut self, tok: Token<'s>) -> Result<Position, ParseError> {
match self.peek()? {
(p, t) if t == tok => {
self.next()?;
Ok(p)
},
(p, t) => Err(self.err_at(format!("Unexpected token {t:?}, expected {tok:?}"), p)),
}
}
fn err_here<S>(&mut self, msg: S) -> ParseError
where S: Into<String> {
ParseError { msg: msg.into(), pos: self.peek().map(|(p,_)| p).ok() }
}
fn err_at<S>(&mut self, msg: S, pos: Position) -> ParseError
where S: Into<String> {
ParseError { msg: msg.into(), pos: Some(pos) }
}
fn expr(&mut self, min_prec: u32) -> Result<Expr<'s>, ParseError> {
let (pos, tok) = self.next()?;
let mut expr = match tok { let mut expr = match tok {
Token::Number(n) => Expr::Number(Complex::from(n)), Token::Number(n) => Expr::Number(Complex::from(n)),
Token::Name(n) => Expr::Name(n), Token::Name(n) => Expr::Name(n),
@ -115,37 +142,38 @@ impl <'s> Parser<'s> {
tok => if let Some(op) = UnaryOp::from_token(tok) { tok => if let Some(op) = UnaryOp::from_token(tok) {
Expr::Unary(op, Box::new(self.expr(op.precedence())?)) Expr::Unary(op, Box::new(self.expr(op.precedence())?))
} else { } else {
return Err(ParseError::Unexpected(pos, tok)) return Err(self.err_at(format!("Unexpected token {:?}", tok), pos))
} }
}; };
while let Some((_, tok)) = self.scanner.peek() { while let Ok((_, tok)) = self.peek() {
if is_closing(&tok) {
break;
}
expr = match tok { expr = match tok {
Token::Equal | Token::Colon | Token::RParen | Token::Newline | Token::Comma => break,
Token::LParen => { Token::LParen => {
self.scanner.next(); self.next()?;
let mut args = Vec::new(); let mut args = Vec::new();
while !matches!(self.scanner.peek(), None | Some((_, Token::RParen))) { while !matches!(self.peek(), Err(_) | Ok((_, Token::RParen))) {
args.push(self.expr(0)?); args.push(self.expr(0)?);
match self.scanner.peek() { match self.peek()?.1 {
Some((_, Token::Comma)) => { self.scanner.next(); }, Token::Comma => { self.next()?; },
Some((_, Token::RParen)) => break, Token::RParen => break,
Some((pos, _)) => return Err(ParseError::Expected(*pos, Token::RParen)), _ => return Err(self.err_here(format!("Unexpected token {:?}, expected ',' or ')'", tok)))
None => return Err(ParseError::UnexpectedEof),
} }
} }
self.expect(Token::RParen)?; self.expect(Token::RParen)?;
match expr { match expr {
Expr::Name(name) => Expr::FnCall(name, args), Expr::Name(name) => Expr::FnCall(name, args),
_ => return Err(ParseError::InvalidFunction(expr)), _ => return Err(self.err_here("Cannot call this expression"))
} }
}, },
tok => if let Some(op) = BinaryOp::from_token(*tok) { tok => if let Some(op) = BinaryOp::from_token(tok) {
let (lp, rp) = op.precedence(); let (lp, rp) = op.precedence();
if lp < min_prec { if lp < min_prec {
break; break;
} }
self.scanner.next(); self.next()?;
let rhs = self.expr(rp)?; let rhs = self.expr(rp)?;
Expr::Binary(op, Box::new(expr), Box::new(rhs)) Expr::Binary(op, Box::new(expr), Box::new(rhs))
} else { } else {
@ -162,98 +190,83 @@ impl <'s> Parser<'s> {
Ok(expr) Ok(expr)
} }
pub fn parse_stmt_equals(&mut self, lhs_pos: Position, lhs: Expr<'s>) -> Result<Stmt<'s>, ParseError<'s>> { fn stmt(&mut self) -> Result<Stmt<'s>, ParseError> {
if self.scanner.peek().is_none() { let expr = self.expr(0)?;
return Err(ParseError::UnexpectedEof) match self.peek()?.1 {
} Token::Arrow => {
self.next()?;
let rhs = self.expr(0)?; let name = match self.next()? {
(_, Token::Name(name)) => name,
if self.scanner.peek().is_some() { (p, t) => return Err(self.err_at(format!("Unexpected token {t:?}, expected a name"), p))
self.expect(Token::Newline)?;
}
match lhs {
Expr::Name(name) => Ok(Stmt::Const { name, body: rhs }),
Expr::FnCall(name, args) => {
let mut arg_names = Vec::with_capacity(args.len());
for arg in args {
if let Expr::Name(name) = arg {
arg_names.push(name);
}
}
Ok(Stmt::Func { name, args: arg_names, body: rhs })
}
_ => return Err(ParseError::InvalidLValue(lhs_pos))
}
}
pub fn parse_stmt_deriv(&mut self, lhs_pos: Position, lhs: Expr<'s>) -> Result<Stmt<'s>, ParseError<'s>> {
if self.scanner.peek().is_none() {
return Err(ParseError::UnexpectedEof)
}
let pos = self.scanner.peek().unwrap().0;
let f = self.expr(0)?;
match (lhs, f) {
(Expr::Name(name), Expr::Name(func)) => Ok(Stmt::Deriv { name, func }),
(Expr::Name(_), f) => Err(ParseError::UnexpectedExpr(pos, f)),
(lhs, _) => Err(ParseError::UnexpectedExpr(lhs_pos, lhs)),
}
}
pub fn parse_stmt_iter(&mut self, lhs_pos: Position, lhs: Expr<'s>) -> Result<Stmt<'s>, ParseError<'s>> {
let Expr::Name(name) = lhs else {
return Err(ParseError::UnexpectedExpr(lhs_pos, lhs))
}; };
if self.scanner.peek().is_none() { Ok(Stmt::Store(name, expr))
return Err(ParseError::UnexpectedEof)
} }
let pos = self.scanner.peek().unwrap().0; _ => Ok(Stmt::Expr(expr))
let func = self.expr(0)?;
let Expr::Name(func) = func else {
return Err(ParseError::UnexpectedExpr(pos, func))
};
self.expect(Token::Comma)?;
if self.scanner.peek().is_none() {
return Err(ParseError::UnexpectedEof)
} }
let pos = self.scanner.peek().unwrap().0;
let count = self.expr(0)?;
let Expr::Number(count) = count else {
return Err(ParseError::UnexpectedExpr(pos, count))
};
Ok(Stmt::Iter { name, func, count: count.re as u32 })
} }
pub fn parse(&mut self) -> Result<Vec<Stmt<'s>>, ParseError<'s>> { fn stmts(&mut self) -> Result<Vec<Stmt<'s>>, ParseError> {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
while self.scanner.peek().is_some() { loop {
while matches!(self.scanner.peek(), Some((_, Token::Newline))) { stmts.push(self.stmt()?);
self.scanner.next(); if !matches!(self.peek(), Ok((_, Token::Comma))) {
break
} }
self.next()?;
if self.scanner.peek().is_none() {
break;
}
let lhs_pos = self.scanner.peek().unwrap().0;
let lhs = self.expr(0)?;
let stmt = match self.scanner.next() {
Some((_, Token::Equal)) => self.parse_stmt_equals(lhs_pos, lhs)?,
Some((_, Token::Colon)) => match self.scanner.next() {
Some((_, Token::Name("deriv"))) => self.parse_stmt_deriv(lhs_pos, lhs)?,
Some((_, Token::Name("iter"))) => self.parse_stmt_iter(lhs_pos, lhs)?,
Some((pos, tok)) => return Err(ParseError::Unexpected(pos, tok)),
None => return Err(ParseError::UnexpectedEof),
}
Some((pos, tok)) => return Err(ParseError::Unexpected(pos, tok)),
None => return Err(ParseError::UnexpectedEof),
};
stmts.push(stmt);
} }
Ok(stmts) Ok(stmts)
} }
pub fn parse(&mut self) -> Result<Vec<Defn<'s>>, ParseError> {
println!("parse");
let mut defns = Vec::new();
while self.peek().is_ok() {
println!("parse loop");
while matches!(self.peek(), Ok((_, Token::Newline))) {
self.next()?;
}
if self.peek().is_err() {
break;
}
let lhspos = self.peek()?.0;
let lhs = self.expr(0)?;
self.expect(Token::Equal)?;
let defn = match lhs {
Expr::Name(name) => {
let rhs = self.expr(0)?;
Defn::Const { name, body: rhs }
},
Expr::FnCall(name, args) => {
let mut rhs = self.stmts()?;
let last = rhs.pop().ok_or(self.err_here("Empty function body"))?;
let Stmt::Expr(last) = last else {
return Err(self.err_here("Last statement in function body must be a plain expression"))
};
let args = args.iter()
.map(|a| match a {
Expr::Name(n) => Ok(*n),
_ => Err(self.err_at("Invalid function declaration", lhspos))
}).collect::<Result<Vec<&str>, ParseError>>()?;
Defn::Func { name, args, body: (rhs, last) }
},
_ => return Err(self.err_at("Invalid lvalue, expected a name or function call", lhspos)),
};
defns.push(defn);
if self.at_end() {
break
}
self.expect(Token::Newline)?;
}
Ok(defns)
}
}
fn is_closing(tok: &Token) -> bool {
matches!(tok, Token::Equal | Token::RParen | Token::Newline | Token::Comma | Token::Arrow)
} }

View file

@ -14,7 +14,7 @@ pub enum Token<'s> {
Caret, Caret,
Equal, Equal,
Comma, Comma,
Colon, Arrow,
LParen, LParen,
RParen, RParen,
Newline, Newline,
@ -90,11 +90,13 @@ impl <'s> Scanner<'s> {
let tok = match self.next().unwrap() { let tok = match self.next().unwrap() {
'\n' => Token::Newline, '\n' => Token::Newline,
'+' => Token::Plus, '+' => Token::Plus,
'-' => Token::Minus, '-' => match self.peek().unwrap() {
'>' => { self.next(); Token::Arrow },
_ => Token::Minus,
}
'*' => Token::Star, '*' => Token::Star,
'/' => Token::Slash, '/' => Token::Slash,
'^' => Token::Caret, '^' => Token::Caret,
':' => Token::Colon,
'=' => Token::Equal, '=' => Token::Equal,
',' => Token::Comma, ',' => Token::Comma,
'(' => Token::LParen, '(' => Token::LParen,

View file

@ -8,9 +8,7 @@ fn main() {
.init(); .init();
let src = r#" let src = r#"
f(z, c) = z^2 + c plot(z) = z^2 -> w, z*w
g: iter f, 100
plot(z) = g(z, z)
"#; "#;
let wgsl = compile(src).unwrap(); let wgsl = compile(src).unwrap();
println!("{wgsl}"); println!("{wgsl}");

View file

@ -207,7 +207,7 @@ impl WgpuState {
} }
pub fn resize(&mut self, size: (u32, u32)) { pub fn resize(&mut self, size: (u32, u32)) {
let size = (size.0.max(1).min(2048), size.1.max(1).min(2048)); let size = (size.0.max(1).min(8192), size.1.max(1).min(8192));
self.config.width = size.0; self.config.width = size.0;
self.config.height = size.1; self.config.height = size.1;
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);

View file

@ -190,16 +190,22 @@ fn shademap(r: f32) -> f32 {
return r*inverseSqrt(r * r + 0.0625 * uniforms.shading_intensity)*0.9875 + 0.0125; return r*inverseSqrt(r * r + 0.0625 * uniforms.shading_intensity)*0.9875 + 0.0125;
} }
@fragment fn colorfor(w: vec2f) -> vec3f {
fn fs_main(@builtin(position) in: vec4f) -> @location(0) vec4f { let z = func_plot(w);
let pos = vec2(in.x, f32(uniforms.resolution.y) - in.y);
var z = remap(pos, vec2(0.0), vec2f(uniforms.resolution), uniforms.bounds_min, uniforms.bounds_max);
z = func_plot(z);
let r = sqrt(z.x*z.x + z.y*z.y); let r = sqrt(z.x*z.x + z.y*z.y);
let arg = atan2(z.y, z.x); let arg = atan2(z.y, z.x);
let hsv = vec3f(arg / TAU + 1.0, shademap(1.0/r), shademap(r)); let hsv = vec3f(arg / TAU + 1.0, shademap(1.0/r), shademap(r));
let col = pow(hsv2rgb(hsv), vec3(2.0)); return pow(hsv2rgb(hsv), vec3(2.0));
}
@fragment
fn fs_main(@builtin(position) in: vec4f) -> @location(0) vec4f {
let pos = vec2(in.x, f32(uniforms.resolution.y) - in.y);
let z = remap(pos, vec2(0.0), vec2f(uniforms.resolution), uniforms.bounds_min, uniforms.bounds_max);
//let dz = z - remap(pos + vec2f(1.0), vec2(0.0), vec2f(uniforms.resolution), uniforms.bounds_min, uniforms.bounds_max);
let col = colorfor(z);
return vec4f(col, 1.0); return vec4f(col, 1.0);
} }

View file

@ -26,7 +26,7 @@ pub async fn start() {
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let window = Window::new(&event_loop).unwrap(); let window = Window::new(&event_loop).unwrap();
window.set_inner_size(PhysicalSize::new(1200, 1200)); window.set_inner_size(PhysicalSize::new(100, 100));
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {

View file

@ -4,32 +4,44 @@
} }
body { body {
height: 100%; min-height: 100vh;
max-height: 100vh;
overflow: hidden; overflow: hidden;
--header-height: 50px;
--sidebar-width: 500px;
} }
#main { #main {
min-height: 100vh; min-height: 100vh;
display: flex; max-height: 100vh;
flex-direction: column;
} }
#header { #header {
height: 50px; height: var(--header-height);
} }
#content { #content {
display: flex;
flex: 1;
width: 100vw; width: 100vw;
height: calc(100vh - var(--header-height));
display: flex;
} }
#sidebar { #sidebar {
flex: 1; width: var(--sidebar-width);
height: inherit;
} }
#canvas_container { #canvas_container {
flex: 3; width: calc(100vw - var(--sidebar-width));
height: inherit;
}
#sidebar textarea {
width: calc(100% - 24px);
margin-left: 10px;
height: 99%;
resize: none;
font-size: 16px;
} }
canvas { canvas {