js,gg: more work on porting gg to JS backend (#12903)

pull/12908/head
playX 2021-12-20 16:18:21 +03:00 committed by GitHub
parent f81654e3a7
commit 5f0160bf11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 392 additions and 219 deletions

View File

@ -34,6 +34,63 @@ pub mut:
framebuffer_height int framebuffer_height int
} }
pub struct Config {
pub:
width int
height int
use_ortho bool // unused, still here just for backwards compatibility
retina bool
resizable bool
user_data voidptr
font_size int
create_window bool
// window_user_ptr voidptr
window_title string
borderless_window bool
always_on_top bool
bg_color gx.Color
init_fn FNCb = voidptr(0)
frame_fn FNCb = voidptr(0)
native_frame_fn FNCb = voidptr(0)
cleanup_fn FNCb = voidptr(0)
fail_fn FNFail = voidptr(0)
//
event_fn FNEvent = voidptr(0)
quit_fn FNEvent = voidptr(0)
//
keydown_fn FNKeyDown = voidptr(0)
keyup_fn FNKeyUp = voidptr(0)
char_fn FNChar = voidptr(0)
//
move_fn FNMove = voidptr(0)
click_fn FNClick = voidptr(0)
unclick_fn FNUnClick = voidptr(0)
leave_fn FNEvent = voidptr(0)
enter_fn FNEvent = voidptr(0)
resized_fn FNEvent = voidptr(0)
scroll_fn FNEvent = voidptr(0)
// wait_events bool // set this to true for UIs, to save power
fullscreen bool
scale f32 = 1.0
sample_count int
swap_interval int = 1 // 1 = 60fps, 2 = 30fps etc. The preferred swap interval (ignored on some platforms)
// ved needs this
// init_text bool
font_path string
custom_bold_font_path string
ui_mode bool // refreshes only on events to save CPU usage
// font bytes for embedding
font_bytes_normal []byte
font_bytes_bold []byte
font_bytes_mono []byte
font_bytes_italic []byte
native_rendering bool // Cocoa on macOS/iOS, GDI+ on Windows
// drag&drop
enable_dragndrop bool // enable file dropping (drag'n'drop), default is false
max_dropped_files int = 1 // max number of dropped files to process (default: 1)
max_dropped_file_path_length int = 2048 // max length in bytes of a dropped UTF-8 file path (default: 2048)
}
[heap] [heap]
pub struct Context { pub struct Context {
mut: mut:

View File

@ -1,5 +1,6 @@
module gg module gg
import gx
import js.dom import js.dom
pub enum DOMEventType { pub enum DOMEventType {
@ -55,37 +56,6 @@ pub mut:
framebuffer_height int framebuffer_height int
} }
pub struct Context {
mut:
render_text bool = true
image_cache []Image
needs_refresh bool = true
ticks int
pub mut:
scale f32 = 1.0
width int
height int
window JS.Window
config Config
user_data voidptr
ui_mode bool
frame u64
mbtn_mask byte
mouse_buttons MouseButtons
mouse_pos_x int
mouse_pos_y int
mouse_dx int
mouse_dy int
scroll_x int
scroll_y int
//
key_modifiers Modifier // the current key modifiers
key_repeat bool // whether the pressed key was an autorepeated one
pressed_keys [key_code_max]bool // an array representing all currently pressed keys
pressed_keys_edge [key_code_max]bool // true when the previous state of pressed_keys,
// *before* the current event was different
}
pub enum DOMMouseButton { pub enum DOMMouseButton {
invalid = -1 invalid = -1
left = 0 left = 0
@ -226,3 +196,152 @@ pub enum DOMKeyCode {
right_super = 347 right_super = 347
menu = 348 menu = 348
} }
pub struct Config {
pub:
width int
height int
use_ortho bool // unused, still here just for backwards compatibility
retina bool
resizable bool
user_data voidptr
font_size int
create_window bool
// window_user_ptr voidptr
window_title string
borderless_window bool
always_on_top bool
bg_color gx.Color
init_fn FNCb = voidptr(0)
frame_fn FNCb = voidptr(0)
native_frame_fn FNCb = voidptr(0)
cleanup_fn FNCb = voidptr(0)
fail_fn FNFail = voidptr(0)
//
event_fn FNEvent = voidptr(0)
quit_fn FNEvent = voidptr(0)
//
keydown_fn FNKeyDown = voidptr(0)
keyup_fn FNKeyUp = voidptr(0)
char_fn FNChar = voidptr(0)
//
move_fn FNMove = voidptr(0)
click_fn FNClick = voidptr(0)
unclick_fn FNUnClick = voidptr(0)
leave_fn FNEvent = voidptr(0)
enter_fn FNEvent = voidptr(0)
resized_fn FNEvent = voidptr(0)
scroll_fn FNEvent = voidptr(0)
// wait_events bool // set this to true for UIs, to save power
fullscreen bool
scale f32 = 1.0
sample_count int
swap_interval int = 1 // 1 = 60fps, 2 = 30fps etc. The preferred swap interval (ignored on some platforms)
// ved needs this
// init_text bool
font_path string
custom_bold_font_path string
ui_mode bool // refreshes only on events to save CPU usage
// font bytes for embedding
font_bytes_normal []byte
font_bytes_bold []byte
font_bytes_mono []byte
font_bytes_italic []byte
native_rendering bool // Cocoa on macOS/iOS, GDI+ on Windows
// drag&drop
enable_dragndrop bool // enable file dropping (drag'n'drop), default is false
max_dropped_files int = 1 // max number of dropped files to process (default: 1)
max_dropped_file_path_length int = 2048 // max length in bytes of a dropped UTF-8 file path (default: 2048)
context JS.CanvasRenderingContext2D
}
pub struct Context {
mut:
render_text bool = true
image_cache []Image
needs_refresh bool = true
ticks int
pub mut:
scale f32 = 1.0
width int
height int
window JS.Window [noinit]
config Config
user_data voidptr
ui_mode bool
frame u64
mbtn_mask byte
mouse_buttons MouseButtons
mouse_pos_x int
mouse_pos_y int
mouse_dx int
mouse_dy int
scroll_x int
scroll_y int
//
key_modifiers Modifier // the current key modifiers
key_repeat bool // whether the pressed key was an autorepeated one
pressed_keys [key_code_max]bool // an array representing all currently pressed keys
pressed_keys_edge [key_code_max]bool // true when the previous state of pressed_keys,
canvas JS.CanvasRenderingContext2D [noinit]
// *before* the current event was different
}
pub fn new_context(cfg Config) &Context {
mut g := &Context{}
g.user_data = cfg.user_data
g.width = cfg.width
g.height = cfg.height
g.ui_mode = cfg.ui_mode
g.config = cfg
if isnil(cfg.user_data) {
g.user_data = g
}
g.window = dom.window()
g.canvas = cfg.context
return g
}
pub fn (mut ctx Context) run() {
gg_animation_frame_fn(mut ctx)
}
pub fn (mut ctx Context) begin() {
// ctx.canvas.beginPath()
}
pub fn (mut ctx Context) end() {
// ctx.canvas.closePath()
}
pub fn (mut ctx Context) draw_line(x1 f32, y1 f32, x2 f32, y2 f32, c gx.Color) {
ctx.canvas.beginPath()
ctx.canvas.strokeStyle = c.to_css_string().str
ctx.canvas.moveTo(x1, y1)
ctx.canvas.lineTo(x2, y2)
ctx.canvas.stroke()
ctx.canvas.closePath()
}
pub fn (mut ctx Context) draw_rect(x f32, y f32, w f32, h f32, c gx.Color) {
ctx.canvas.beginPath()
ctx.canvas.fillStyle = c.to_css_string().str
ctx.canvas.fillRect(x, y, w, h)
ctx.canvas.closePath()
}
fn gg_animation_frame_fn(mut g Context) {
g.frame++
g.canvas.clearRect(0, 0, g.config.width, g.config.height)
// todo(playXE): handle events
if !isnil(g.config.frame_fn) {
f := g.config.frame_fn
f(g.user_data)
g.needs_refresh = false
}
g.window.requestAnimationFrame(fn [mut g] (time JS.Number) {
gg_animation_frame_fn(mut g)
})
}

View File

@ -23,63 +23,6 @@ pub type FNUnClick = fn (x f32, y f32, button MouseButton, data voidptr)
pub type FNChar = fn (c u32, data voidptr) pub type FNChar = fn (c u32, data voidptr)
pub struct Config {
pub:
width int
height int
use_ortho bool // unused, still here just for backwards compatibility
retina bool
resizable bool
user_data voidptr
font_size int
create_window bool
// window_user_ptr voidptr
window_title string
borderless_window bool
always_on_top bool
bg_color gx.Color
init_fn FNCb = voidptr(0)
frame_fn FNCb = voidptr(0)
native_frame_fn FNCb = voidptr(0)
cleanup_fn FNCb = voidptr(0)
fail_fn FNFail = voidptr(0)
//
event_fn FNEvent = voidptr(0)
quit_fn FNEvent = voidptr(0)
//
keydown_fn FNKeyDown = voidptr(0)
keyup_fn FNKeyUp = voidptr(0)
char_fn FNChar = voidptr(0)
//
move_fn FNMove = voidptr(0)
click_fn FNClick = voidptr(0)
unclick_fn FNUnClick = voidptr(0)
leave_fn FNEvent = voidptr(0)
enter_fn FNEvent = voidptr(0)
resized_fn FNEvent = voidptr(0)
scroll_fn FNEvent = voidptr(0)
// wait_events bool // set this to true for UIs, to save power
fullscreen bool
scale f32 = 1.0
sample_count int
swap_interval int = 1 // 1 = 60fps, 2 = 30fps etc. The preferred swap interval (ignored on some platforms)
// ved needs this
// init_text bool
font_path string
custom_bold_font_path string
ui_mode bool // refreshes only on events to save CPU usage
// font bytes for embedding
font_bytes_normal []byte
font_bytes_bold []byte
font_bytes_mono []byte
font_bytes_italic []byte
native_rendering bool // Cocoa on macOS/iOS, GDI+ on Windows
// drag&drop
enable_dragndrop bool // enable file dropping (drag'n'drop), default is false
max_dropped_files int = 1 // max number of dropped files to process (default: 1)
max_dropped_file_path_length int = 2048 // max length in bytes of a dropped UTF-8 file path (default: 2048)
}
pub struct PenConfig { pub struct PenConfig {
color gx.Color color gx.Color
line_type PenLineType = .solid line_type PenLineType = .solid

View File

@ -228,3 +228,113 @@ pub fn (ctx &Context) text_size(s string) (int, int) {
ctx.ft.fons.text_bounds(0, 0, s, &buf[0]) ctx.ft.fons.text_bounds(0, 0, s, &buf[0])
return int((buf[2] - buf[0]) / ctx.scale), int((buf[3] - buf[1]) / ctx.scale) return int((buf[2] - buf[0]) / ctx.scale), int((buf[3] - buf[1]) / ctx.scale)
} }
pub fn system_font_path() string {
env_font := os.getenv('VUI_FONT')
if env_font != '' && os.exists(env_font) {
return env_font
}
$if windows {
debug_font_println('Using font "C:\\Windows\\Fonts\\arial.ttf"')
return 'C:\\Windows\\Fonts\\arial.ttf'
}
$if macos {
fonts := ['/System/Library/Fonts/SFNS.ttf', '/System/Library/Fonts/SFNSText.ttf',
'/Library/Fonts/Arial.ttf']
for font in fonts {
if os.is_file(font) {
debug_font_println('Using font "$font"')
return font
}
}
}
$if android {
xml_files := ['/system/etc/system_fonts.xml', '/system/etc/fonts.xml',
'/etc/system_fonts.xml', '/etc/fonts.xml', '/data/fonts/fonts.xml',
'/etc/fallback_fonts.xml']
font_locations := ['/system/fonts', '/data/fonts']
for xml_file in xml_files {
if os.is_file(xml_file) && os.is_readable(xml_file) {
xml := os.read_file(xml_file) or { continue }
lines := xml.split('\n')
mut candidate_font := ''
for line in lines {
if line.contains('<font') {
candidate_font = line.all_after('>').all_before('<').trim(' \n\t\r')
if candidate_font.contains('.ttf') {
for location in font_locations {
candidate_path := os.join_path(location, candidate_font)
if os.is_file(candidate_path) && os.is_readable(candidate_path) {
debug_font_println('Using font "$candidate_path"')
return candidate_path
}
}
}
}
}
}
}
}
mut fm := os.execute("fc-match --format='%{file}\n' -s")
if fm.exit_code == 0 {
lines := fm.output.split('\n')
for l in lines {
if !l.contains('.ttc') {
debug_font_println('Using font "$l"')
return l
}
}
} else {
panic('fc-match failed to fetch system font')
}
panic('failed to init the font')
}
fn get_font_path_variant(font_path string, variant FontVariant) string {
// TODO: find some way to make this shorter and more eye-pleasant
// NotoSans, LiberationSans, DejaVuSans, Arial and SFNS should work
mut file := os.file_name(font_path)
mut fpath := font_path.replace(file, '')
file = file.replace('.ttf', '')
match variant {
.normal {}
.bold {
if fpath.ends_with('-Regular') {
file = file.replace('-Regular', '-Bold')
} else if file.starts_with('DejaVuSans') {
file += '-Bold'
} else if file.to_lower().starts_with('arial') {
file += 'bd'
} else {
file += '-bold'
}
$if macos {
if os.exists('SFNS-bold') {
file = 'SFNS-bold'
}
}
}
.italic {
if file.ends_with('-Regular') {
file = file.replace('-Regular', '-Italic')
} else if file.starts_with('DejaVuSans') {
file += '-Oblique'
} else if file.to_lower().starts_with('arial') {
file += 'i'
} else {
file += 'Italic'
}
}
.mono {
if !file.ends_with('Mono-Regular') && file.ends_with('-Regular') {
file = file.replace('-Regular', 'Mono-Regular')
} else if file.to_lower().starts_with('arial') {
// Arial has no mono variant
} else {
file += 'Mono'
}
}
}
return fpath + file + '.ttf'
}

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by an MIT license that can be found in the LICENSE file. // Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module gg module gg
import os
import gx import gx
enum FontVariant { enum FontVariant {
@ -30,116 +29,6 @@ struct StringToRender {
cfg gx.TextCfg cfg gx.TextCfg
} }
pub fn system_font_path() string {
env_font := os.getenv('VUI_FONT')
if env_font != '' && os.exists(env_font) {
return env_font
}
$if windows {
debug_font_println('Using font "C:\\Windows\\Fonts\\arial.ttf"')
return 'C:\\Windows\\Fonts\\arial.ttf'
}
$if macos {
fonts := ['/System/Library/Fonts/SFNS.ttf', '/System/Library/Fonts/SFNSText.ttf',
'/Library/Fonts/Arial.ttf']
for font in fonts {
if os.is_file(font) {
debug_font_println('Using font "$font"')
return font
}
}
}
$if android {
xml_files := ['/system/etc/system_fonts.xml', '/system/etc/fonts.xml',
'/etc/system_fonts.xml', '/etc/fonts.xml', '/data/fonts/fonts.xml',
'/etc/fallback_fonts.xml']
font_locations := ['/system/fonts', '/data/fonts']
for xml_file in xml_files {
if os.is_file(xml_file) && os.is_readable(xml_file) {
xml := os.read_file(xml_file) or { continue }
lines := xml.split('\n')
mut candidate_font := ''
for line in lines {
if line.contains('<font') {
candidate_font = line.all_after('>').all_before('<').trim(' \n\t\r')
if candidate_font.contains('.ttf') {
for location in font_locations {
candidate_path := os.join_path(location, candidate_font)
if os.is_file(candidate_path) && os.is_readable(candidate_path) {
debug_font_println('Using font "$candidate_path"')
return candidate_path
}
}
}
}
}
}
}
}
mut fm := os.execute("fc-match --format='%{file}\n' -s")
if fm.exit_code == 0 {
lines := fm.output.split('\n')
for l in lines {
if !l.contains('.ttc') {
debug_font_println('Using font "$l"')
return l
}
}
} else {
panic('fc-match failed to fetch system font')
}
panic('failed to init the font')
}
fn get_font_path_variant(font_path string, variant FontVariant) string {
// TODO: find some way to make this shorter and more eye-pleasant
// NotoSans, LiberationSans, DejaVuSans, Arial and SFNS should work
mut file := os.file_name(font_path)
mut fpath := font_path.replace(file, '')
file = file.replace('.ttf', '')
match variant {
.normal {}
.bold {
if fpath.ends_with('-Regular') {
file = file.replace('-Regular', '-Bold')
} else if file.starts_with('DejaVuSans') {
file += '-Bold'
} else if file.to_lower().starts_with('arial') {
file += 'bd'
} else {
file += '-bold'
}
$if macos {
if os.exists('SFNS-bold') {
file = 'SFNS-bold'
}
}
}
.italic {
if file.ends_with('-Regular') {
file = file.replace('-Regular', '-Italic')
} else if file.starts_with('DejaVuSans') {
file += '-Oblique'
} else if file.to_lower().starts_with('arial') {
file += 'i'
} else {
file += 'Italic'
}
}
.mono {
if !file.ends_with('Mono-Regular') && file.ends_with('-Regular') {
file = file.replace('-Regular', 'Mono-Regular')
} else if file.to_lower().starts_with('arial') {
// Arial has no mono variant
} else {
file += 'Mono'
}
}
}
return fpath + file + '.ttf'
}
[if debug_font ?] [if debug_font ?]
fn debug_font_println(s string) { fn debug_font_println(s string) {
println(s) println(s)

View File

@ -232,3 +232,7 @@ const (
pub fn color_from_string(s string) Color { pub fn color_from_string(s string) Color {
return gx.string_colors[s] return gx.string_colors[s]
} }
pub fn (c Color) to_css_string() string {
return 'rgb($c.r,$c.g,$c.b)'
}

14
vlib/gx/text.js.v 100644
View File

@ -0,0 +1,14 @@
module gx
pub enum HorizontalAlign {
left
center
right
}
pub enum VerticalAlign {
top
middle
bottom
baseline
}

View File

@ -451,7 +451,7 @@ pub interface JS.CanvasRenderingContext2D {
getLineDash() JS.Array getLineDash() JS.Array
setLineDash(segments JS.Array) setLineDash(segments JS.Array)
clearRect(x JS.Number, y JS.Number, w JS.Number, h JS.Number) clearRect(x JS.Number, y JS.Number, w JS.Number, h JS.Number)
fillRect(x JS.Number, y JS.Number, w JS.null, h JS.Number) fillRect(x JS.Number, y JS.Number, w JS.Number, h JS.Number)
strokeRect(x JS.Number, y JS.Number, w JS.Number, h JS.Number) strokeRect(x JS.Number, y JS.Number, w JS.Number, h JS.Number)
getTransformt() JS.DOMMatrix getTransformt() JS.DOMMatrix
resetTransform() resetTransform()

View File

@ -3,6 +3,7 @@ module os
$if js_node { $if js_node {
#global.$ENV = $process.env #global.$ENV = $process.env
} $else { } $else {
#const global = $global;
#global.$ENV = {} #global.$ENV = {}
} }

View File

@ -7,8 +7,9 @@ pub mut:
is_opened bool is_opened bool
} }
#const $buffer = require('buffer'); $if !js_browser {
#const $buffer = require('buffer');
}
// todo(playX): __as_cast is broken here // todo(playX): __as_cast is broken here
/* /*
pub struct ErrFileNotOpened { pub struct ErrFileNotOpened {

View File

@ -1,8 +1,10 @@
module os module os
#const $fs = require('fs'); $if js_node {
#const $path = require('path'); #const $fs = require('fs');
#const tty = require('tty') #const $path = require('path');
#const tty = require('tty')
}
pub const ( pub const (
path_delimiter = '/' path_delimiter = '/'

View File

@ -1,6 +1,8 @@
module os module os
#const $child_process = require('child_process') $if js_node {
#const $child_process = require('child_process')
}
// new_process - create a new process descriptor // new_process - create a new process descriptor
// NB: new does NOT start the new process. // NB: new does NOT start the new process.

View File

@ -827,7 +827,14 @@ pub fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type {
} }
// Do not allow empty uninitialized interfaces // Do not allow empty uninitialized interfaces
sym := c.table.sym(field.typ) sym := c.table.sym(field.typ)
if sym.kind == .interface_ { mut has_noinit := false
for attr in field.attrs {
if attr.name == 'noinit' {
has_noinit = true
break
}
}
if sym.kind == .interface_ && (!has_noinit && sym.language != .js) {
// TODO: should be an error instead, but first `ui` needs updating. // TODO: should be an error instead, but first `ui` needs updating.
c.note('interface field `${type_sym.name}.$field.name` must be initialized', c.note('interface field `${type_sym.name}.$field.name` must be initialized',
node.pos) node.pos)

View File

@ -461,10 +461,11 @@ pub fn (mut g JsGen) init() {
g.definitions.writeln('const \$process = {') g.definitions.writeln('const \$process = {')
g.definitions.writeln(' arch: "js",') g.definitions.writeln(' arch: "js",')
if g.pref.backend == .js_freestanding { if g.pref.backend == .js_freestanding {
g.definitions.writeln(' platform: "freestanding"') g.definitions.writeln(' platform: "freestanding",')
} else { } else {
g.definitions.writeln(' platform: "browser"') g.definitions.writeln(' platform: "browser",')
} }
g.definitions.writeln(' cwd: function() { return "" }')
g.definitions.writeln('}') g.definitions.writeln('}')
g.definitions.writeln('const \$os = {') g.definitions.writeln('const \$os = {')
@ -3263,12 +3264,8 @@ fn (mut g JsGen) gen_struct_init(it ast.StructInit) {
g.writeln('return tmp') g.writeln('return tmp')
g.dec_indent() g.dec_indent()
g.writeln('})()') g.writeln('})()')
} else { } else if type_sym.kind == .struct_ && type_sym.language == .js {
if type_sym.kind == .struct_ && type_sym.language == .js { g.writeln('{')
g.writeln('{')
} else {
g.writeln('new ${g.js_name(name)}({')
}
g.inc_indent() g.inc_indent()
for i, field in it.fields { for i, field in it.fields {
if field.name.len != 0 { if field.name.len != 0 {
@ -3281,11 +3278,26 @@ fn (mut g JsGen) gen_struct_init(it ast.StructInit) {
g.writeln('') g.writeln('')
} }
g.dec_indent() g.dec_indent()
if type_sym.kind == .struct_ && type_sym.language == .js {
g.writeln('}') g.writeln('}')
} else { } else {
g.writeln('})') g.writeln('(function() {')
g.inc_indent()
tmp := g.new_tmp_var()
g.writeln('let $tmp = new ${g.js_name(name)}({});')
for field in it.fields {
if field.name.len != 0 {
g.write('${tmp}.$field.name = ')
g.expr(field.expr)
}
g.write(';')
g.writeln('')
} }
g.writeln('return $tmp;')
g.dec_indent()
g.writeln('})()')
} }
} }
@ -3385,6 +3397,18 @@ fn (mut g JsGen) gen_type_cast_expr(it ast.CastExpr) {
return return
} }
if (from_type_sym.name == 'Any' && from_type_sym.language == .js)
|| from_type_sym.name == 'JS.Any' {
if it.typ.is_ptr() {
g.write('new \$ref(')
}
g.expr(it.expr)
if it.typ.is_ptr() {
g.write(')')
}
return
}
// Skip cast if type is the same as the parrent caster // Skip cast if type is the same as the parrent caster
tsym := to_type_sym tsym := to_type_sym
if tsym.kind == .sum_type { if tsym.kind == .sum_type {