diff --git a/.gitignore b/.gitignore index caadf47d2..2848ec502 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ nakefile test/main *.exe build/ +.debug +.vscode \ No newline at end of file diff --git a/doc/context.md b/doc/context.md new file mode 100644 index 000000000..449f46ef3 --- /dev/null +++ b/doc/context.md @@ -0,0 +1,17 @@ +Context +======= + +WindowView +---------- + +Interfaces with window and graphics backend. + +Each view has a graphics context. + +TODO: The context may be unique or shared between windows depending on backend. + +A view may need app data and backend data to properly initialize itself. + +Some backends (js) do not currently support sharing between opengl contexts. + + diff --git a/editor/nakefile.nim b/editor/nakefile.nim index cf6bc3539..39ea82c13 100644 --- a/editor/nakefile.nim +++ b/editor/nakefile.nim @@ -11,3 +11,9 @@ preprocessResources = proc(b: Builder) = let sf = f.splitFile() if sf.ext == ".nimx": b.copyResourceAsIs(f.replace("res/", "")) + +task "debug", "Build for debugging": + let b = newBuilder() + b.additionalNimFlags.add "--debugger:on" + b.runAfterBuild = false + b.build() diff --git a/editor/nimxedit.nim b/editor/nimxedit.nim index b8e50812a..f6f76c1e6 100644 --- a/editor/nimxedit.nim +++ b/editor/nimxedit.nim @@ -1,16 +1,4 @@ -import tables - -import nimx/matrixes -import nimx/system_logger -import nimx/animation -import nimx/image -import nimx/window -import nimx/autotest -import nimx/button, nimx/text_field -import nimx/all_views -import nimx/editor/edit_view - -const isMobile = defined(ios) or defined(android) +import nimx proc runAutoTestsIfNeeded() = uiTest generalUITest: @@ -22,7 +10,7 @@ proc runAutoTestsIfNeeded() = startRegisteredTests() proc startApplication() = - when isMobile: + when mobile: var mainWindow = newFullscreenWindow() else: var mainWindow = newWindow(newRect(40, 40, 1200, 600)) diff --git a/kiwi b/kiwi new file mode 160000 index 000000000..64d1c62c1 --- /dev/null +++ b/kiwi @@ -0,0 +1 @@ +Subproject commit 64d1c62c1eb07538371990e15a257bcb5fb4b7a9 diff --git a/nakefile.nim b/nakefile.nim index 9ac14c9ed..2e4032bbb 100644 --- a/nakefile.nim +++ b/nakefile.nim @@ -34,3 +34,9 @@ task "docs", "Build documentation": direShell "nim rst2html " & f & " &>/dev/null" copyDir "../js", "./livedemo" + +task "debug", "Build for debugging": + let b = newBuilder() + b.additionalNimFlags.add "--debugger:on" + b.runAfterBuild = false + b.build() diff --git a/nim_emscripten_tutorial b/nim_emscripten_tutorial new file mode 160000 index 000000000..0640d403b --- /dev/null +++ b/nim_emscripten_tutorial @@ -0,0 +1 @@ +Subproject commit 0640d403bf84f6eac78da971006252463a808c20 diff --git a/nimsl b/nimsl new file mode 160000 index 000000000..491120cef --- /dev/null +++ b/nimsl @@ -0,0 +1 @@ +Subproject commit 491120cefac5bcf1bd7fbaa096ff80cd7c688d92 diff --git a/nimx.nim b/nimx.nim new file mode 100644 index 000000000..523eee43a --- /dev/null +++ b/nimx.nim @@ -0,0 +1,31 @@ +import nimx / [ + backends, + window, + autotest, +] + +# Views +import nimx / [ + button, + text_field, + image_view, + slider, + segmented_control, + collection_view, + editor/edit_view, +] + +export + # core + backends, + window, + autotest, + + # views + button, + text_field, + image_view, + slider, + segmented_control, + collection_view, + edit_view diff --git a/nimx.nimble b/nimx.nimble index da60d3709..42e85e6be 100644 --- a/nimx.nimble +++ b/nimx.nimble @@ -1,6 +1,6 @@ # Package -version = "0.1" +version = "0.2" author = "Yuriy Glukhov" description = "GUI framework" license = "MIT" diff --git a/nimx/abstract_window.nim b/nimx/abstract_window.nim index 9d29df41e..bb4d402cd 100644 --- a/nimx/abstract_window.nim +++ b/nimx/abstract_window.nim @@ -83,20 +83,20 @@ method drawWindow*(w: Window) {.base.} = w.needsDisplay = false w.recursiveDrawSubviews() - let c = currentContext() + template c: untyped = w.gfx let profiler = sharedProfiler() if profiler.enabled: updateFps() - profiler["Overdraw"] = GetOverdrawValue() - profiler["DIPs"] = GetDIPValue() + profiler["Overdraw"] = getOverdrawValue(w.gfx) + profiler["DIPs"] = getDIPValue(w.gfx) profiler["Animations"] = totalAnims const fontSize = 14 const profilerWidth = 110 - var font = systemFont() + var font = systemFont(c.fontCtx) let old_size = font.size - font.size = fontSize + `size=`(font, c.fontCtx, fontSize) var rect = newRect(w.frame.width - profilerWidth, 5, profilerWidth - 5, Coord(profiler.len) * font.height) c.fillColor = newGrayColor(1, 0.8) c.strokeWidth = 0 @@ -108,9 +108,9 @@ method drawWindow*(w: Window) {.base.} = pt.x = w.frame.width - profilerWidth c.drawText(font, pt, k & ": " & v) pt.y = pt.y + fontSize - font.size = old_size - ResetOverdrawValue() - ResetDIPValue() + `size=`(font, c.fontCtx, old_size) + resetOverdrawValue(c) + resetDIPValue(c) let dc = currentDragSystem() if not dc.pItem.isNil: @@ -125,8 +125,7 @@ method drawWindow*(w: Window) {.base.} = c.drawRect(rect) method draw*(w: Window, rect: Rect) = - let c = currentContext() - let gl = c.gl + template gl: untyped = w.gfx.gl if w.mActiveBgColor != w.backgroundColor: gl.clearColor(w.backgroundColor.r, w.backgroundColor.g, w.backgroundColor.b, w.backgroundColor.a) w.mActiveBgColor = w.backgroundColor @@ -203,12 +202,14 @@ proc onFocusChange*(w: Window, inFocus: bool)= sharedNotificationCenter().postNotification(AW_FOCUS_LEAVE) var newWindow*: proc(r: Rect): Window +var newWindowWithGfxContext*: proc(gfx: GraphicsContext, r: Rect): Window var newFullscreenWindow*: proc(): Window +var newFullscreenWindowWithGfxContext*: proc(gfx: GraphicsContext): Window var newWindowWithNative*: proc(handle: pointer, r: Rect): Window var newFullscreenWindowWithNative*: proc(handle: pointer): Window -method init*(w: Window, frame: Rect) = - procCall w.View.init(frame) +method init*(w: Window, gfx: GraphicsContext, frame: Rect) = + procCall w.View.init(gfx, frame) w.window = w w.needsDisplay = true w.mCurrentTouches = newTable[int, View]() diff --git a/nimx/autotest.nim b/nimx/autotest.nim index 45eaa77e9..5099db3e1 100644 --- a/nimx/autotest.nim +++ b/nimx/autotest.nim @@ -1,5 +1,5 @@ import macros, logging, strutils -import nimx / [ timer, app, event, abstract_window, button ] +import nimx / [ backends, timer, app, event, window, button ] type UITestSuiteStep* = tuple code : proc() @@ -10,8 +10,6 @@ type UITestSuite* = ref object name: string steps: seq[UITestSuiteStep] -const web = defined(js) or defined(emscripten) or defined(wasm) - when web: when defined(emscripten) or defined(wasm): import jsbind/emscripten diff --git a/nimx/backends.nim b/nimx/backends.nim new file mode 100644 index 000000000..32cf0977f --- /dev/null +++ b/nimx/backends.nim @@ -0,0 +1,70 @@ +type OsApi* {.pure.} = enum + web + android + ios + macosx + linux + windows + +type WindowApi* {.pure.} = enum + web + sdl + x11 + appkit + winapi + +type InputApi* {.pure.} = enum + web + sdl + x11 + appkit + winapi + +type GraphicApi* {.pure.} = enum + opengles2 + +type AudioApi* {.pure.} = enum + web + sdl + appkit + winapi + alsa + +type Backend* = tuple + os: OsApi + win: WindowApi + input: InputApi + gfx: GraphicApi + audio: AudioApi + +const backend*: Backend = + when defined js: + (OsApi.web, WindowApi.web, InputApi.web, GraphicApi.opengles2, AudioApi.web) + elif defined emscripten: + (OsApi.web, WindowApi.web, InputApi.web, GraphicApi.opengles2, AudioApi.web) + elif defined wasm: + (OsApi.web, WindowApi.web, InputApi.web, GraphicApi.opengles2, AudioApi.web) + elif defined ios: + (OsApi.ios, WindowApi.sdl, InputApi.sdl, GraphicApi.opengles2, AudioApi.sdl) + elif defined android: + (OsApi.android, WindowApi.sdl, InputApi.sdl, GraphicApi.opengles2, AudioApi.sdl) + elif defined macosx: + when defined nimxAvoidSDL: + (OsApi.macosx, WindowApi.appkit, InputApi.appkit, GraphicApi.opengles2, AudioApi.appkit) + else: + (OsApi.macosx, WindowApi.sdl, InputApi.sdl, GraphicApi.opengles2, AudioApi.sdl) + elif defined linux: + when defined nimxAvoidSDL: + (OsApi.linux, WindowApi.xll, InputApi.xll, GraphicApi.opengles2, AudioApi.alsa) + else: + (OsApi.linux, WindowApi.sdl, InputApi.sdl, GraphicApi.opengles2, AudioApi.sdl) + elif defined windows: + when defined nimxAvoidSDL: + (OsApi.windows, WindowApi.winapi, InputApi.winapi, GraphicApi.opengles2, AudioApi.winapi) + else: + (OsApi.windows, WindowApi.sdl, InputApi.sdl, GraphicApi.opengles2, AudioApi.sdl) + else: {.error: "unknown backend".} + +const web* = backend.os == OsApi.web +const mobile* = defined(ios) or defined(android) +const desktop* = not web and not mobile diff --git a/nimx/button.nim b/nimx/button.nim index 24d49eda7..9432b6a71 100644 --- a/nimx/button.nim +++ b/nimx/button.nim @@ -52,38 +52,38 @@ Button.properties: imageMarginLeft image -proc newButton*(r: Rect): Button = +proc newButton*(gfx: GraphicsContext, r: Rect): Button = result.new() - result.init(r) + result.init(gfx, r) -proc newButton*(parent: View = nil, position: Point = newPoint(0, 0), size: Size = newSize(100, 20), title: string = "Button"): Button = - result = newButton(newRect(position.x, position.y, size.width, size.height)) +proc newButton*(parent: View = nil, gfx: GraphicsContext, position: Point = newPoint(0, 0), size: Size = newSize(100, 20), title: string = "Button"): Button = + result = newButton(gfx, newRect(position.x, position.y, size.width, size.height)) result.title = title if not isNil(parent): parent.addSubview(result) -proc newCheckbox*(r: Rect): Button = - result = newButton(r) +proc newCheckbox*(gfx: GraphicsContext, r: Rect): Button = + result = newButton(gfx, r) result.style = bsCheckbox result.behavior = bbToggle -proc newRadiobox*(r: Rect): Button = - result = newButton(r) +proc newRadiobox*(gfx: GraphicsContext, r: Rect): Button = + result = newButton(gfx, r) result.style = bsRadiobox result.behavior = bbToggle -proc newImageButton*(r: Rect): Button = - result = newButton(r) +proc newImageButton*(gfx: GraphicsContext, r: Rect): Button = + result = newButton(gfx, r) result.style = bsImage -proc newImageButton*(parent: View = nil, position: Point = newPoint(0, 0), size: Size = newSize(100, 20), image: Image = nil): Button = - result = newImageButton(newRect(position.x, position.y, size.width, size.height)) +proc newImageButton*(parent: View = nil, gfx: GraphicsContext, position: Point = newPoint(0, 0), size: Size = newSize(100, 20), image: Image = nil): Button = + result = newImageButton(gfx, newRect(position.x, position.y, size.width, size.height)) result.image = image if not isNil(parent): parent.addSubview(result) -method init*(b: Button, frame: Rect) = - procCall b.Control.init(frame) +method init*(b: Button, gfx: GraphicsContext, frame: Rect) = + procCall b.Control.init(gfx, frame) b.state = bsUp b.enabled = true b.backgroundColor = whiteColor() @@ -93,29 +93,31 @@ method init*(b: Button, frame: Rect) = b.imageMarginTop = 2 b.imageMarginBottom = 2 -method init*(b: Checkbox, frame: Rect) = - procCall b.Button.init(frame) +method init*(b: Checkbox, gfx: GraphicsContext, frame: Rect) = + procCall b.Button.init(gfx, frame) b.style = bsCheckbox b.behavior = bbToggle -method init*(b: Radiobox, frame: Rect) = - procCall b.Button.init(frame) +method init*(b: Radiobox, gfx: GraphicsContext, frame: Rect) = + procCall b.Button.init(gfx, frame) b.style = bsRadiobox b.behavior = bbToggle proc drawTitle(b: Button, xOffset: Coord) = + template gfx: untyped = b.gfx + template fontCtx: untyped = b.gfx.fontCtx + template gl: untyped = b.gfx.gl if b.title.len != 0: - let c = currentContext() - c.fillColor = if b.state == bsDown and b.style == bsRegular: + gfx.fillColor = if b.state == bsDown and b.style == bsRegular: whiteColor() else: blackColor() - let font = systemFont() + let font = systemFont(fontCtx) var titleRect = b.bounds - var pt = centerInRect(font.sizeOfString(b.title), titleRect) + var pt = centerInRect(sizeOfString(fontCtx, gl, font, b.title), titleRect) if pt.x < xOffset: pt.x = xOffset - c.drawText(font, pt, b.title) + gfx.drawText(font, pt, b.title) var regularButtonComposition = newComposition """ uniform vec4 uStrokeColor; @@ -134,7 +136,7 @@ void compose() { """ proc drawRegularBezel(b: Button) = - regularButtonComposition.draw b.bounds: + draw b.gfx, regularButtonComposition, b.bounds: if b.state == bsUp: setUniform("uStrokeColor", newGrayColor(0.78)) setUniform("uFillColorStart", if b.enabled: b.backgroundColor else: grayColor()) @@ -162,10 +164,10 @@ proc drawCheckboxStyle(b: Button, r: Rect) = let size = b.bounds.height bezelRect = newRect(0, 0, size, size) - c = currentContext() + c = b.gfx if b.value != 0: - checkButtonComposition.draw bezelRect: + draw c, checkButtonComposition, bezelRect: setUniform("uStrokeColor", selectionColor) setUniform("uFillColor", selectionColor) setUniform("uRadius", 4.0) @@ -182,7 +184,7 @@ proc drawCheckboxStyle(b: Button, r: Rect) = c.drawLine(newPoint(size / 4.0, size * 1.0 / 2.0), newPoint(size / 4.0 * 2.0, size * 1.0 / 2.0 + size / 5.0 - c.strokeWidth / 2.0)) c.drawLine(newPoint(size / 4.0 * 2.0 - c.strokeWidth / 2.0, size * 1.0 / 2.0 + size / 5.0), newPoint(size / 4.0 * 3.0 - c.strokeWidth / 2.0, size / 4.0)) else: - checkButtonComposition.draw bezelRect: + draw c, checkButtonComposition, bezelRect: setUniform("uStrokeColor", newGrayColor(0.78)) setUniform("uFillColor", whiteColor()) setUniform("uRadius", 4.0) @@ -206,16 +208,17 @@ void compose() { proc drawRadioboxStyle(b: Button, r: Rect) = let bezelRect = newRect(0, 0, b.bounds.height, b.bounds.height) + template c: untyped = b.gfx # Selected if b.value != 0: - radioButtonComposition.draw bezelRect: + draw c, radioButtonComposition, bezelRect: setUniform("uStrokeColor", selectionColor) setUniform("uFillColor", selectionColor) setUniform("uRadioValue", bezelRect.height * 0.3) setUniform("uStrokeWidth", 0.0) else: - radioButtonComposition.draw bezelRect: + draw c, radioButtonComposition, bezelRect: setUniform("uStrokeColor", newGrayColor(0.78)) setUniform("uFillColor", whiteColor()) setUniform("uRadioValue", 1.0) @@ -224,7 +227,7 @@ proc drawRadioboxStyle(b: Button, r: Rect) = b.drawTitle(bezelRect.width + 1) proc drawImage(b: Button) = - let c = currentContext() + template c: untyped = b.gfx let r = b.bounds if b.imageMarginLeft != 0 or b.imageMarginRight != 0 or b.imageMarginTop != 0 or b.imageMarginBottom != 0: @@ -315,12 +318,8 @@ method onTouchEv*(b: Button, e: var Event): bool = result = b.handleToggleTouchEv(e) registerClass(Button) - -const checkBox = proc(): RootRef= newCheckbox(zeroRect) -registerClass(Checkbox, checkBox) - -const radiButton = proc(): RootRef = newRadiobox(zeroRect) -registerClass(Radiobox, radiButton) +registerClass(Checkbox) +registerClass(Radiobox) genVisitorCodeForView(Button) genVisitorCodeForView(Checkbox) diff --git a/nimx/class_registry.nim b/nimx/class_registry.nim index 8818d1b1b..8f5e2dacd 100644 --- a/nimx/class_registry.nim +++ b/nimx/class_registry.nim @@ -96,13 +96,13 @@ template registerClass*(a: typedesc) = return res registerClass(a, c) -template isClassRegistered*(name: string): bool = name in classFactory - proc newObjectOfClass*(name: string): RootRef = let c = classFactory.getOrDefault(name) if c.creatorProc.isNil: raise newException(Exception, "Class '" & name & "' is not registered") result = c.creatorProc() +template isClassRegistered*(name: string): bool = name in classFactory + iterator registeredClasses*(): string = for k in classFactory.keys: yield k diff --git a/nimx/clip_view.nim b/nimx/clip_view.nim index 32c83c504..91a6967dc 100644 --- a/nimx/clip_view.nim +++ b/nimx/clip_view.nim @@ -1,11 +1,12 @@ +import context import nimx / view import nimx / meta_extensions / [ property_desc, visitors_gen, serializers_gen ] type ClipView* = ref object of View -proc newClipView*(r: Rect): ClipView = +proc newClipView*(gfx: GraphicsContext, r: Rect): ClipView = result.new() - result.init(r) + result.init(gfx, r) result.autoresizingMask = { afFlexibleWidth, afFlexibleHeight } method subviewDidChangeDesiredSize*(v: ClipView, sub: View, desiredSize: Size) = diff --git a/nimx/collection_view.nim b/nimx/collection_view.nim index 1c91a4f6d..413f7b980 100644 --- a/nimx/collection_view.nim +++ b/nimx/collection_view.nim @@ -40,13 +40,13 @@ const proc updateLayout*(v: CollectionView) -proc newCollectionView*(r: Rect, itemSize: Size, layoutDirection: LayoutDirection, layoutWidth: int = LayoutWidthAuto): CollectionView = +proc newCollectionView*(gfx: GraphicsContext, r: Rect, itemSize: Size, layoutDirection: LayoutDirection, layoutWidth: int = LayoutWidthAuto): CollectionView = ## CollectionView constructor result.new() result.layoutDirection = layoutDirection result.layoutWidth = layoutWidth result.itemSize = itemSize - result.init(r) + result.init(gfx, r) method clipType*(v: CollectionView): ClipType = ctDefaultClip @@ -162,8 +162,8 @@ proc updateLayout*(v: CollectionView) = v.scrollOffset = 0.0 v.update() -method init*(v: CollectionView, r: Rect) = - procCall v.View.init(r) +method init*(v: CollectionView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.rangeCache.dirty = true v.offset = 2.0 let scrollListener = new(CollectionScrollListener) @@ -204,7 +204,6 @@ CollectionView.properties: layoutWidth itemSize -const collectionCreator = proc(): RootRef = newCollectionView(zeroRect, zeroSize, LeftToRight) -registerClass(CollectionView, collectionCreator) +registerClass(CollectionView) genVisitorCodeForView(CollectionView) genSerializeCodeForView(CollectionView) diff --git a/nimx/color_picker.nim b/nimx/color_picker.nim index 9bab5a5d9..28ac656df 100644 --- a/nimx/color_picker.nim +++ b/nimx/color_picker.nim @@ -75,9 +75,9 @@ type template enclosingColorPickerView(v: View): ColorPickerView = v.enclosingViewOfType(ColorPickerView) -proc newColorComponentTextField(r: Rect, comp: ColorComponent): ColorComponentTextField = +proc newColorComponentTextField(gfx: GraphicsContext, r: Rect, comp: ColorComponent): ColorComponentTextField = result.new - result.init(r) + result.init(gfx, r) result.cComponent = comp result.textColor = newGrayColor(0.0) @@ -138,13 +138,13 @@ proc hsvToRgb(color: tuple[h: float, s: float, v: float]): Color = # ColorPickerH -proc newColorPickerH(r: Rect): ColorPickerH = +proc newColorPickerH(gfx: GraphicsContext, r: Rect): ColorPickerH = ## Hue picker constructor result.new - result.init(r) + result.init(gfx, r) -method init(cph: ColorPickerH, r: Rect) = - procCall cph.View.init(r) +method init(cph: ColorPickerH, gfx: GraphicsContext, r: Rect) = + procCall cph.View.init(gfx, r) var cpHComposition = newComposition """ uniform float uChosenH; @@ -163,10 +163,10 @@ var cpHComposition = newComposition """ method draw(cph: ColorPickerH, r: Rect) = ## Drawing Hue picker - let c = currentContext() + template c: untyped = cph.gfx let h = cph.enclosingColorPickerView().currentColor.h - cpHComposition.draw r: + draw c, cpHComposition, r: setUniform("uChosenH", h) proc colorHasChanged(cpv: ColorPickerView) = @@ -214,13 +214,13 @@ method onTouchEv(cph: ColorPickerH, e: var Event): bool = # ColorPickerS -proc newColorPickerS(r: Rect): ColorPickerS = +proc newColorPickerS(gfx: GraphicsContext, r: Rect): ColorPickerS = ## Saturation picker constructor result.new - result.init(r) + result.init(gfx, r) -method init(cps: ColorPickerS, r: Rect) = - procCall cps.View.init(r) +method init(cps: ColorPickerS, gfx: GraphicsContext, r: Rect) = + procCall cps.View.init(gfx, r) var cpSComposition = newComposition """ uniform float uHcps; @@ -240,10 +240,10 @@ var cpSComposition = newComposition """ method draw(cps: ColorPickerS, r: Rect) = ## Drawing Hue picker - let c = currentContext() + template c: untyped = cps.gfx let cc = cps.enclosingColorPickerView().currentColor - cpSComposition.draw r: + draw c, cpSComposition, r: setUniform("uHcps", cc.h) setUniform("uChosenS", cc.s) @@ -263,13 +263,13 @@ method onTouchEv(cps: ColorPickerS, e: var Event): bool = # ColorPickerV -proc newColorPickerV(r: Rect): ColorPickerV = +proc newColorPickerV(gfx: GraphicsContext, r: Rect): ColorPickerV = ## Saturation picker constructor result.new - result.init(r) + result.init(gfx, r) -method init(cpv: ColorPickerV, r: Rect) = - procCall cpv.View.init(r) +method init(cpv: ColorPickerV, gfx: GraphicsContext, r: Rect) = + procCall cpv.View.init(gfx, r) var cpVComposition = newComposition """ uniform float uHcpv; @@ -289,10 +289,10 @@ var cpVComposition = newComposition """ method draw(cpv: ColorPickerV, r: Rect) = ## Drawing Hue picker - let c = currentContext() + template c: untyped = cpv.gfx let cc = cpv.enclosingColorPickerView().currentColor - cpVComposition.draw r: + draw c, cpVComposition, r: setUniform("uHcpv", cc.h) setUniform("uChosenV", cc.v) @@ -312,8 +312,8 @@ method onTouchEv(cpva: ColorPickerV, e: var Event): bool = # ColorPickerCircle -proc newColorPickerCircle(defaultPalette: ColorPickerPalette, radius: Coord, frame: Rect): ColorPickerCircle = - result = ColorPickerCircle.new(frame) +proc newColorPickerCircle(gfx: GraphicsContext, defaultPalette: ColorPickerPalette, radius: Coord, frame: Rect): ColorPickerCircle = + result = ColorPickerCircle.new(gfx, frame) result.radius = radius result.palette = defaultPalette @@ -348,14 +348,14 @@ var hsvCircleComposition = newComposition """ method draw*(cpc: ColorPickerCircle, r: Rect) = ## Custom palette drawing - let c = currentContext() + template c: untyped = cpc.gfx let cpv = cpc.enclosingColorPickerView() # Draw hsv circle c.fillColor = newGrayColor(0.0, 0.0) c.strokeColor = newGrayColor(0.0, 0.0) - hsvCircleComposition.draw cpc.bounds: + draw c, hsvCircleComposition, cpc.bounds: setUniform("uHsvValue", 1.0) setUniform("uChosenH", cpv.currentColor.h) @@ -377,10 +377,10 @@ method onTouchEv*(cpc: ColorPickerCircle, e: var Event): bool = # ColorPickerView -proc newColorPickerView*(r: Rect, defaultPalette = ColorPickerPalette.HSV, backgroundColor: Color = newGrayColor(0.35, 0.8)): ColorPickerView = +proc newColorPickerView*(gfx: GraphicsContext, r: Rect = newRect(0, 0, 300, 200), defaultPalette = ColorPickerPalette.HSV, backgroundColor: Color = newGrayColor(0.35, 0.8)): ColorPickerView = ## ColorPickerView constructor result.new - result.init(r) + result.init(gfx, r) result.palette = defaultPalette result.backgroundColor = backgroundColor result.enableDraggingByBackground() @@ -400,15 +400,15 @@ proc addToHistory*(cpv: ColorPickerView, color: Color) = # ColorView -proc newColorView*(r: Rect, color: Color, main: bool = false): ColorView = +proc newColorView*(gfx: GraphicsContext, r: Rect, color: Color, main: bool = false): ColorView = ## Reactable Color quad constructor result.new - result.init(r) + result.init(gfx, r) result.backgroundColor = color result.main = main -method init(cv: ColorView, r: Rect) = - procCall cv.View.init(r) +method init(cv: ColorView, gfx: GraphicsContext, r: Rect) = + procCall cv.View.init(gfx, r) cv.backgroundColor = newGrayColor(1.0) method onTouchEv(cv: ColorView, e: var Event): bool = @@ -424,9 +424,9 @@ method onTouchEv(cv: ColorView, e: var Event): bool = return true -method init*(cpv: ColorPickerView, r: Rect) = +method init*(cpv: ColorPickerView, gfx: GraphicsContext, r: Rect) = # Basic Properties Initialization - procCall cpv.View.init(r) + procCall cpv.View.init(gfx, r) cpv.colorHistory = @[] cpv.rightMargin = r.width * 2.0 / 3.0 @@ -435,7 +435,7 @@ method init*(cpv: ColorPickerView, r: Rect) = let circleRadius = (rightSize - 2.0 * margin) / 2.0 let circleRect = newRect(cpv.rightMargin + margin, margin, rightSize - margin, rightSize - margin) - cpv.circle = newColorPickerCircle(ColorPickerPalette.HSV, circleRadius, circleRect) + cpv.circle = newColorPickerCircle(gfx, ColorPickerPalette.HSV, circleRadius, circleRect) cpv.addSubview(cpv.circle) # Color Palette Popup Chooser @@ -445,13 +445,14 @@ method init*(cpv: ColorPickerView, r: Rect) = cpv.paletteChooser = newPopupButton( cpv, + gfx, newPoint(cpv.rightMargin + margin, rightSize + paletteSize / 2 - paletteHeight / 2), newSize(rightSize - margin * 2, paletteHeight), items = @["HSV"] ) # Current Chosen Color Quad - cpv.chosenColorView = newColorView(newRect(margin * 2.0 + 20, margin * 2.0, r.height / 4.0, r.height / 4.0), newGrayColor(1.0), main = true) + cpv.chosenColorView = newColorView(gfx, newRect(margin * 2.0 + 20, margin * 2.0, r.height / 4.0, r.height / 4.0), newGrayColor(1.0), main = true) cpv.addSubview(cpv.chosenColorView) let coff = r.height - (20 * 3 + margin * 4) @@ -461,7 +462,7 @@ method init*(cpv: ColorPickerView, r: Rect) = let historySize = (historyPixelSize.int / (r.height / 8.0 + margin.float).int).int for hitem in 0 ..< historySize: - let newItem = newColorView(newRect( + let newItem = newColorView(gfx, newRect( 20 + margin * 3.0 + r.height / 4.0 + r.height / 8.0 * hitem.float + (margin * hitem + 1).float, margin * 2.0 + r.height / 8.0, r.height / 8.0, @@ -473,32 +474,32 @@ method init*(cpv: ColorPickerView, r: Rect) = # HSV Components # HSV Components Labels - let hLabel = newLabel(cpv, newPoint(margin, coff + margin), newSize(20, 20), "H:") - let sLabel = newLabel(cpv, newPoint(margin, coff + margin * 2 + 20), newSize(20, 20), "S:") - let vLabel = newLabel(cpv, newPoint(margin, coff + margin * 3 + 40), newSize(20, 20), "V:") + let hLabel = newLabel(cpv, gfx, newPoint(margin, coff + margin), newSize(20, 20), "H:") + let sLabel = newLabel(cpv, gfx, newPoint(margin, coff + margin * 2 + 20), newSize(20, 20), "S:") + let vLabel = newLabel(cpv, gfx, newPoint(margin, coff + margin * 3 + 40), newSize(20, 20), "V:") # H Component View - cpv.tfH = newColorComponentTextField(newRect(margin + 20 + margin, coff + margin, 40, 20), ColorComponent.H) + cpv.tfH = newColorComponentTextField(gfx, newRect(margin + 20 + margin, coff + margin, 40, 20), ColorComponent.H) cpv.tfH.text = $cpv.currentColor.h cpv.addSubview(cpv.tfH) - cpv.cpH = newColorPickerH(newRect(margin + 20 + 40 + margin + margin, coff + margin, r.width - rightSize - 40 - margin * 4.0 - 20, 20)) + cpv.cpH = newColorPickerH(gfx, newRect(margin + 20 + 40 + margin + margin, coff + margin, r.width - rightSize - 40 - margin * 4.0 - 20, 20)) cpv.addSubview(cpv.cpH) # S Component View - cpv.tfS = newColorComponentTextField(newRect(margin + 20 + margin, coff + margin * 2 + 20, 40, 20), ColorComponent.S) + cpv.tfS = newColorComponentTextField(gfx, newRect(margin + 20 + margin, coff + margin * 2 + 20, 40, 20), ColorComponent.S) cpv.tfS.text = $cpv.currentColor.s cpv.addSubview(cpv.tfS) - cpv.cpS = newColorPickerS(newRect(margin + 20 + 40 + margin + margin, coff + margin * 2 + 20, r.width - rightSize - 40 - margin * 4.0 - 20, 20)) + cpv.cpS = newColorPickerS(gfx, newRect(margin + 20 + 40 + margin + margin, coff + margin * 2 + 20, r.width - rightSize - 40 - margin * 4.0 - 20, 20)) cpv.addSubview(cpv.cpS) # V Component View - cpv.tfV = newColorComponentTextField(newRect(margin + 20 + margin, coff + margin * 3 + 40, 40, 20), ColorComponent.V) + cpv.tfV = newColorComponentTextField(gfx, newRect(margin + 20 + margin, coff + margin * 3 + 40, 40, 20), ColorComponent.V) cpv.tfV.text = $cpv.currentColor.v cpv.addSubview(cpv.tfV) - cpv.cpV = newColorPickerV(newRect(margin + 20 + 40 + margin + margin, coff + margin * 3 + 40, r.width - rightSize - 40 - margin * 4.0 - 20, 20)) + cpv.cpV = newColorPickerV(gfx, newRect(margin + 20 + 40 + margin + margin, coff + margin * 3 + 40, r.width - rightSize - 40 - margin * 4.0 - 20, 20)) cpv.addSubview(cpv.cpV) proc `color=`*(v: ColorPickerView, c: Color) = @@ -507,13 +508,6 @@ proc `color=`*(v: ColorPickerView, c: Color) = proc color*(v: ColorPickerView): Color = hsvToRGB(v.currentColor) -var gColorPicker: ColorPickerView - -proc sharedColorPicker*(): ColorPickerView = - if gColorPicker.isNil: - gColorPicker = newColorPickerView(newRect(0, 0, 300, 200)) - result = gColorPicker - proc popupAtPoint*(c: ColorPickerView, v: View, p: Point) = c.removeFromSuperview() c.setFrameOrigin(v.convertPointToWindow(p)) @@ -522,7 +516,6 @@ proc popupAtPoint*(c: ColorPickerView, v: View, p: Point) = ColorPickerView.properties: rightMargin -const colorCreat = proc(): RootRef = newColorPickerView(zeroRect) -registerClass(ColorPickerView, colorCreat) +registerClass(ColorPickerView) genVisitorCodeForView(ColorPickerView) genSerializeCodeForView(ColorPickerView) diff --git a/nimx/common/expand_button.nim b/nimx/common/expand_button.nim index 0d813c563..5bf5a35ab 100644 --- a/nimx/common/expand_button.nim +++ b/nimx/common/expand_button.nim @@ -5,8 +5,8 @@ type ExpandButton* = ref object of Button expanded*: bool onExpandAction*: proc(state: bool) -method init*(b: ExpandButton, r: Rect) = - procCall b.Button.init(r) +method init*(b: ExpandButton, gfx: GraphicsContext, r: Rect) = + procCall b.Button.init(gfx, r) b.enable() b.onAction() do(): @@ -14,15 +14,15 @@ method init*(b: ExpandButton, r: Rect) = if not b.onExpandAction.isNil: b.onExpandAction(b.expanded) -proc newExpandButton*(r: Rect): ExpandButton = +proc newExpandButton*(gfx: GraphicsContext, r: Rect): ExpandButton = result.new() - result.init(r) + result.init(gfx, r) -proc newExpandButton*(v: View, r: Rect): ExpandButton = - result = newExpandButton(r) +proc newExpandButton*(v: View, gfx: GraphicsContext, r: Rect): ExpandButton = + result = newExpandButton(gfx, r) v.addSubview(result) method draw*(b: ExpandButton, r: Rect) = - let c = currentContext() + template c: untyped = b.gfx c.fillColor = newColor(0.9, 0.9, 0.9) c.drawTriangle(r, if b.expanded: Coord(PI / 2.0) else: Coord(0)) diff --git a/nimx/composition.nim b/nimx/composition.nim index 35d1ccc77..189d4b348 100644 --- a/nimx/composition.nim +++ b/nimx/composition.nim @@ -1,10 +1,10 @@ -import context, types, portable_gl, image +import context, composition_types, types, portable_gl, image import private/helper_macros import strutils, tables, hashes import nimsl/nimsl export portable_gl -export context +export context, composition_types const commonDefinitions = """ #define PI 3.14159265359 @@ -255,20 +255,6 @@ proc vertexShader(aPosition: Vec2, uModelViewProjectionMatrix: Mat4, uBounds: Ve const vertexShaderCode = getGLSLVertexShader(vertexShader) type - PostEffect* = ref object - source*: string - setupProc*: proc(cc: CompiledComposition) - mainProcName*: string - seenFlag: bool # Used on compilation phase, should not be used elsewhere. - id*: int - argTypes*: seq[string] - - CompiledComposition* = ref object - program*: ProgramRef - uniformLocations*: seq[UniformLocation] - iTexIndex*: GLint - iUniform*: int - Composition* = object definition: string vsDefinition: string # Vertex shader source code @@ -277,8 +263,6 @@ type options*: int id*: int -var programCache = initTable[Hash, CompiledComposition]() - const posAttr : GLuint = 0 @@ -344,13 +328,6 @@ template newComposition*(definition: static[string], requiresPrequel: bool = tru template newCompositionWithNimsl*(mainProc: typed): Composition = newComposition(getGLSLFragmentShader(mainProc, "compose"), false) -type PostEffectStackElem = object - postEffect: PostEffect - setupProc*: proc(cc: CompiledComposition) - -var postEffectStack = newSeq[PostEffectStackElem]() -var postEffectIdStack = newSeq[Hash]() - proc getPostEffectUniformName(postEffectIndex, argIndex: int, output: var string) = output &= "uPE_" output &= $postEffectIndex @@ -361,7 +338,7 @@ proc postEffectUniformName(postEffectIndex, argIndex: int): string = result = "" getPostEffectUniformName(postEffectIndex, argIndex, result) -proc compileComposition*(gl: GL, comp: Composition, cchash: Hash): CompiledComposition = +proc compileComposition*(ctx: GraphicsContext, comp: Composition, cchash: Hash): CompiledComposition = var fragmentShaderCode = "" if (comp.definition.len != 0 and comp.definition.find("GL_OES_standard_derivatives") < 0) or comp.requiresPrequel: @@ -392,10 +369,10 @@ proc compileComposition*(gl: GL, comp: Composition, cchash: Hash): CompiledCompo fragmentShaderCode &= comp.definition - let ln = postEffectStack.len + let ln = ctx.postEffectStack.len var i = 0 while i < ln: - let pe = postEffectStack[i].postEffect + let pe = ctx.postEffectStack[i].postEffect if not pe.seenFlag: fragmentShaderCode &= pe.source pe.seenFlag = true @@ -407,14 +384,14 @@ proc compileComposition*(gl: GL, comp: Composition, cchash: Hash): CompiledCompo i = 0 while i < ln: - postEffectStack[i].postEffect.seenFlag = false + ctx.postEffectStack[i].postEffect.seenFlag = false inc i fragmentShaderCode &= """void main() { gl_FragColor = vec4(0.0); compose(); """ - i = postEffectStack.len - 1 + i = ctx.postEffectStack.len - 1 while i >= 0: - fragmentShaderCode &= postEffectStack[i].postEffect.mainProcName & "(" - for j in 0 ..< postEffectStack[i].postEffect.argTypes.len: + fragmentShaderCode &= ctx.postEffectStack[i].postEffect.mainProcName & "(" + for j in 0 ..< ctx.postEffectStack[i].postEffect.argTypes.len: if j != 0: fragmentShaderCode &= "," getPostEffectUniformName(i, j, fragmentShaderCode) @@ -427,9 +404,9 @@ proc compileComposition*(gl: GL, comp: Composition, cchash: Hash): CompiledCompo options & vertexShaderCode else: options & comp.vsDefinition - result.program = gl.newShaderProgram(vsCode, fragmentShaderCode, [(posAttr, "aPosition")]) + result.program = ctx.gl.newShaderProgram(vsCode, fragmentShaderCode, [(posAttr, "aPosition")]) result.uniformLocations = newSeq[UniformLocation]() - programCache[cchash] = result + ctx.programCache[cchash] = result proc unwrapPointArray(a: openarray[Point]): seq[GLfloat] = result = newSeq[GLfloat](a.len * 2) @@ -440,8 +417,6 @@ proc unwrapPointArray(a: openarray[Point]): seq[GLfloat] = result[i] = p.y inc i -var texQuad : array[4, GLfloat] - template compositionDrawingDefinitions*(cc: CompiledComposition, ctx: GraphicsContext, gl: GL) = template uniformLocation(name: string): UniformLocation = inc cc.iUniform @@ -490,69 +465,33 @@ template compositionDrawingDefinitions*(cc: CompiledComposition, ctx: GraphicsCo template setUniform(name: string, i: Image) {.hint[XDeclaredButNotUsed]: off.} = gl.activeTexture(GLenum(int(gl.TEXTURE0) + cc.iTexIndex)) - gl.bindTexture(gl.TEXTURE_2D, getTextureQuad(i, gl, texQuad)) - gl.uniform4fv(uniformLocation(name & "_texCoords"), texQuad) + gl.bindTexture(gl.TEXTURE_2D, getTextureQuad(i, gl, ctx.texQuad)) + gl.uniform4fv(uniformLocation(name & "_texCoords"), ctx.texQuad) gl.uniform1i(uniformLocation(name & "_tex"), cc.iTexIndex) inc cc.iTexIndex -template pushPostEffect*(pe: PostEffect, args: varargs[untyped]) = - let stackLen = postEffectIdStack.len - postEffectStack.add(PostEffectStackElem(postEffect: pe, setupProc: proc(cc: CompiledComposition) = - let ctx = currentContext() - let gl = ctx.gl - compositionDrawingDefinitions(cc, ctx, gl) - var j = 0 - staticFor uni in args: - setUniform(postEffectUniformName(stackLen, j), uni) - inc j - )) - - let oh = if stackLen > 0: postEffectIdStack[^1] else: 0 - postEffectIdStack.add(oh !& pe.id) - -template popPostEffect*() = - postEffectStack.setLen(postEffectStack.len - 1) - postEffectIdStack.setLen(postEffectIdStack.len - 1) - -template hasPostEffect*(): bool = - postEffectStack.len > 0 - -template getCompiledComposition*(gl: GL, comp: Composition): CompiledComposition = - let pehash = if postEffectIdStack.len > 0: postEffectIdStack[^1] else: 0 +template getCompiledComposition*(ctx: GraphicsContext, comp: Composition): CompiledComposition = + let pehash = if ctx.postEffectIdStack.len > 0: ctx.postEffectIdStack[^1] else: 0 let cchash = !$(pehash !& comp.id !& comp.options) - var cc = programCache.getOrDefault(cchash) + var cc = ctx.programCache.getOrDefault(cchash) if cc.isNil: - cc = gl.compileComposition(comp, cchash) + cc = compileComposition(ctx, comp, cchash) cc.iUniform = -1 cc.iTexIndex = 0 cc -template setupPosteffectUniforms*(cc: CompiledComposition) = - for pe in postEffectStack: +template setupPosteffectUniforms*(ctx: GraphicsContext, cc: CompiledComposition) = + for pe in ctx.postEffectStack: pe.setupProc(cc) -var overdrawValue = 0'f32 -template GetOverdrawValue*() : float32 = overdrawValue / 1000 - -template ResetOverdrawValue*() = - overdrawValue = 0 - -var DIPValue = 0 -template GetDIPValue*() : int = - DIPValue - -template ResetDIPValue*() = - DIPValue = 0 - -template draw*(comp: var Composition, r: Rect, code: untyped) = +template draw*(ctx: GraphicsContext, comp: var Composition, r: Rect, code: untyped) = block: - let ctx = currentContext() let gl = ctx.gl - let cc = gl.getCompiledComposition(comp) + let cc = getCompiledComposition(ctx, comp) gl.useProgram(cc.program) - overdrawValue += r.size.width * r.size.height - DIPValue += 1 + ctx.overdrawValue += r.size.width * r.size.height + ctx.DIPValue += 1 const componentCount = 2 const vertexCount = 4 @@ -567,12 +506,12 @@ template draw*(comp: var Composition, r: Rect, code: untyped) = setUniform("bounds", r) # This is for fragment shader setUniform("uBounds", r) # This is for vertex shader - setupPosteffectUniforms(cc) + setupPosteffectUniforms(ctx, cc) code gl.drawArrays(gl.TRIANGLE_FAN, 0, vertexCount) gl.bindBuffer(gl.ARRAY_BUFFER, invalidBuffer) -template draw*(comp: var Composition, r: Rect) = - comp.draw r: +template draw*(ctx: GraphicsContext, comp: var Composition, r: Rect) = + draw ctx, comp, r: discard diff --git a/nimx/composition_types.nim b/nimx/composition_types.nim new file mode 100644 index 000000000..e04bf3974 --- /dev/null +++ b/nimx/composition_types.nim @@ -0,0 +1,20 @@ +import portable_gl + +type + CompiledComposition* = ref object + program*: ProgramRef + uniformLocations*: seq[UniformLocation] + iTexIndex*: GLint + iUniform*: int + + PostEffect* = ref object + source*: string + setupProc*: proc(cc: CompiledComposition) + mainProcName*: string + seenFlag*: bool # Used on compilation phase, should not be used elsewhere. + id*: int + argTypes*: seq[string] + + PostEffectStackElem* = object + postEffect*: PostEffect + setupProc*: proc(cc: CompiledComposition) diff --git a/nimx/context.nim b/nimx/context.nim index cfe7eef0f..b2288b86b 100644 --- a/nimx/context.nim +++ b/nimx/context.nim @@ -1,11 +1,13 @@ +import std/[hashes, math, tables] import types import opengl import system_logger import matrixes -import image -import math +import image, timer import portable_gl import nimsl/nimsl +import font +import composition_types export matrixes @@ -15,6 +17,33 @@ type ShaderAttribute* = enum type Transform3D* = Matrix4 +when defined js: + type Transform3DRef* = ref Transform3D +else: + type Transform3DRef* = ptr Transform3D + +type + GraphicsContext* = ref object of RootObj + gl*: GL + pTransform: Transform3DRef + fillColor*: Color + strokeColor*: Color + strokeWidth*: Coord + debugClipColor*: Color + alpha*: Coord + quadIndexBuffer*: BufferRef + gridIndexBuffer4x4*: BufferRef + singleQuadBuffer*: BufferRef + sharedBuffer*: BufferRef + vertexes*: array[4 * 4 * 128, Coord] + texQuad*: array[4, GLfloat] + overdrawValue*: float32 + clippingDepth*: GLint + DIPValue*: int + programCache*: Table[Hash, CompiledComposition] + postEffectStack*: seq[PostEffectStackElem] + postEffectIdStack*: seq[Hash] + fontCtx*: FontContext proc loadShader(gl: GL, shaderSrc: string, kind: GLenum): ShaderRef = result = gl.createShader(kind) @@ -67,26 +96,39 @@ proc newShaderProgram*(gl: GL, vs, fs: string, elif info.len > 0: logi "Program linked: ", info -when defined js: - type Transform3DRef = ref Transform3D -else: - type Transform3DRef = ptr Transform3D - -type GraphicsContext* = ref object of RootObj - gl*: GL - pTransform: Transform3DRef - fillColor*: Color - strokeColor*: Color - strokeWidth*: Coord - debugClipColor: Color - alpha*: Coord - quadIndexBuffer*: BufferRef - gridIndexBuffer4x4: BufferRef - singleQuadBuffer*: BufferRef - sharedBuffer*: BufferRef - vertexes*: array[4 * 4 * 128, Coord] - -var gCurrentContext: GraphicsContext +template getOverdrawValue*(ctx: GraphicsContext): float32 = + ctx.overdrawValue / 1000 + +template resetOverdrawValue*(ctx: GraphicsContext) = + ctx.overdrawValue = 0 + +template getDIPValue*(ctx: GraphicsContext): int = + ctx.DIPValue + +template resetDIPValue*(ctx: GraphicsContext) = + ctx.DIPValue = 0 + +template pushPostEffect*(pe: PostEffect, args: varargs[untyped]) = + template ctx: untyped = pe.gfx + let stackLen = postEffectIdStack.len + ctx.postEffectStack.add(PostEffectStackElem(postEffect: pe, setupProc: proc(cc: CompiledComposition) = + let gl = ctx.gl + compositionDrawingDefinitions(cc, ctx, gl) + var j = 0 + staticFor uni in args: + setUniform(postEffectUniformName(stackLen, j), uni) + inc j + )) + + let oh = if stackLen > 0: ctx.postEffectIdStack[^1] else: 0 + ctx.postEffectIdStack.add(oh !& pe.id) + +template popPostEffect*(ctx: GraphicsContext) = + ctx.postEffectStack.setLen(ctx.postEffectStack.len - 1) + ctx.postEffectIdStack.setLen(ctx.postEffectIdStack.len - 1) + +template hasPostEffect*(ctx: GraphicsContext): bool = + ctx.postEffectStack.len > 0 proc transformToRef(t: Transform3D): Transform3DRef = when defined js: @@ -102,7 +144,7 @@ template withTransform*(c: GraphicsContext, t: Transform3DRef, body: typed) = template withTransform*(c: GraphicsContext, t: Transform3D, body: typed) = c.withTransform(transformToRef(t), body) -template transform*(c: GraphicsContext): var Transform3D = c.pTransform[] +template transform*(ctx: GraphicsContext): var Transform3D = ctx.pTransform[] proc createQuadIndexBuffer*(c: GraphicsContext, numberOfQuads: int): BufferRef = result = c.gl.createBuffer() @@ -181,14 +223,7 @@ proc newGraphicsContext*(canvas: ref RootObj = nil): GraphicsContext = result.singleQuadBuffer = result.createQuadBuffer() result.sharedBuffer = result.gl.createBuffer() - if gCurrentContext.isNil: - gCurrentContext = result - -proc setCurrentContext*(c: GraphicsContext): GraphicsContext {.discardable.} = - result = gCurrentContext - gCurrentContext = c - -template currentContext*(): GraphicsContext = gCurrentContext + result.fontCtx = newFontContext() proc setTransformUniform*(c: GraphicsContext, program: ProgramRef) = c.gl.uniformMatrix4fv(c.gl.getUniformLocation(program, "modelViewProjectionMatrix"), false, c.transform) @@ -236,7 +271,7 @@ void compose() { """ proc drawRoundedRect*(c: GraphicsContext, r: Rect, radius: Coord) = - roundedRectComposition.draw r: + draw c, roundedRectComposition, r: setUniform("uFillColor", c.fillColor) setUniform("uStrokeColor", if c.strokeWidth == 0: c.fillColor else: c.strokeColor) setUniform("uStrokeWidth", c.strokeWidth) @@ -251,7 +286,7 @@ proc drawRect(bounds, uFillColor, uStrokeColor: Vec4, var rectComposition = newCompositionWithNimsl(drawRect) proc drawRect*(c: GraphicsContext, r: Rect) = - rectComposition.draw r: + draw c, rectComposition, r: setUniform("uFillColor", c.fillColor) setUniform("uStrokeColor", if c.strokeWidth == 0: c.fillColor else: c.strokeColor) setUniform("uStrokeWidth", c.strokeWidth) @@ -265,7 +300,7 @@ proc drawEllipse(bounds, uFillColor, uStrokeColor: Vec4, var ellipseComposition = newCompositionWithNimsl(drawEllipse) proc drawEllipseInRect*(c: GraphicsContext, r: Rect) = - ellipseComposition.draw r: + draw c, ellipseComposition, r: setUniform("uFillColor", c.fillColor) setUniform("uStrokeColor", if c.strokeWidth == 0: c.fillColor else: c.strokeColor) setUniform("uStrokeWidth", c.strokeWidth) @@ -301,7 +336,7 @@ proc drawImage*(c: GraphicsContext, i: Image, toRect: Rect, fromRect: Rect = zer if fromRect != zeroRect: let s = i.size fr = newRect(fromRect.x / s.width, fromRect.y / s.height, fromRect.maxX / s.width, fromRect.maxY / s.height) - imageComposition.draw toRect: + draw c, imageComposition, toRect: setUniform("uImage", i) setUniform("uAlpha", alpha * c.alpha) setUniform("uFromRect", fr) @@ -332,7 +367,7 @@ void compose() { proc drawNinePartImage*(c: GraphicsContext, i: Image, toRect: Rect, ml, mt, mr, mb: Coord, fromRect: Rect = zeroRect, alpha: ColorComponent = 1.0) = if i.isLoaded: let gl = c.gl - var cc = gl.getCompiledComposition(ninePartImageComposition) + var cc = getCompiledComposition(c, ninePartImageComposition) var fuv : array[4, GLfloat] let tex = getTextureQuad(i, gl, fuv) @@ -384,7 +419,7 @@ proc drawNinePartImage*(c: GraphicsContext, i: Image, toRect: Rect, ml, mt, mr, setUniform("uAlpha", alpha * c.alpha) gl.uniformMatrix4fv(uniformLocation("uModelViewProjectionMatrix"), false, c.transform) - setupPosteffectUniforms(cc) + setupPosteffectUniforms(c, cc) gl.activeTexture(GLenum(int(gl.TEXTURE0) + cc.iTexIndex)) gl.uniform1i(uniformLocation("texUnit"), cc.iTexIndex) @@ -428,7 +463,7 @@ proc bezierPoint(p0, p1, p2, p3, t: float32): float32 = proc drawBezier*(c: GraphicsContext, p0, p1, p2, p3: Point) = let gl = c.gl - var cc = gl.getCompiledComposition(simpleComposition) + var cc = getCompiledComposition(c, simpleComposition) template setVertex(index: int, p: Point) = c.vertexes[index * 2 + 0] = p.x.GLfloat @@ -445,7 +480,7 @@ proc drawBezier*(c: GraphicsContext, p0, p1, p2, p3: Point) = gl.uniformMatrix4fv(uniformLocation("uModelViewProjectionMatrix"), false, c.transform) setUniform("uStrokeColor", c.strokeColor) - setupPosteffectUniforms(cc) + setupPosteffectUniforms(c, cc) const componentsCount = 2 gl.enableVertexAttribArray(saPosition.GLuint) @@ -498,7 +533,7 @@ proc drawLine*(c: GraphicsContext, pointFrom: Point, pointTo: Point) = let ysize = max(pointFrom.y, pointTo.y) - yfrom let r = newRect(xfrom - c.strokeWidth, yfrom - c.strokeWidth, xsize + 2 * c.strokeWidth, ysize + 2 * c.strokeWidth) - lineComposition.draw r: + draw c, lineComposition, r: setUniform("uStrokeWidth", c.strokeWidth) setUniform("uStrokeColor", c.strokeColor) setUniform("A", pointFrom) @@ -542,7 +577,7 @@ proc drawArc*(c: GraphicsContext, center: Point, radius: Coord, fromAngle, toAng let rad = radius + 1 let r = newRect(center.x - rad, center.y - rad, rad * 2, rad * 2) - arcComposition.draw r: + draw c, arcComposition, r: setUniform("uStrokeWidth", c.strokeWidth) setUniform("uFillColor", c.fillColor) setUniform("uStrokeColor", if c.strokeWidth == 0: c.fillColor else: c.strokeColor) @@ -563,13 +598,10 @@ proc drawTriangle*(c: GraphicsContext, rect: Rect, angleRad: Coord) = ## Draws equilateral triangle with current `fillColor`, pointing at `angleRad` var color = c.fillColor color.a *= c.alpha - triangleComposition.draw rect: + draw c, triangleComposition, rect: setUniform("uAngle", angleRad) setUniform("uColor", color) -# TODO: This should probaly be a property of current context! -var clippingDepth: GLint = 0 - # Clipping proc applyClippingRect*(c: GraphicsContext, r: Rect, on: bool) = c.gl.enable(c.gl.STENCIL_TEST) @@ -577,10 +609,10 @@ proc applyClippingRect*(c: GraphicsContext, r: Rect, on: bool) = c.gl.depthMask(false) c.gl.stencilMask(0xFF) if on: - inc clippingDepth + inc c.clippingDepth c.gl.stencilOp(c.gl.INCR, c.gl.KEEP, c.gl.KEEP) else: - dec clippingDepth + dec c.clippingDepth c.gl.stencilOp(c.gl.DECR, c.gl.KEEP, c.gl.KEEP) c.gl.stencilFunc(c.gl.NEVER, 1, 0xFF) @@ -591,8 +623,8 @@ proc applyClippingRect*(c: GraphicsContext, r: Rect, on: bool) = c.gl.stencilMask(0x00) c.gl.stencilOp(c.gl.KEEP, c.gl.KEEP, c.gl.KEEP) - c.gl.stencilFunc(c.gl.EQUAL, clippingDepth, 0xFF) - if clippingDepth == 0: + c.gl.stencilFunc(c.gl.EQUAL, c.clippingDepth, 0xFF) + if c.clippingDepth == 0: c.gl.disable(c.gl.STENCIL_TEST) template withClippingRect*(c: GraphicsContext, r: Rect, body: typed) = diff --git a/nimx/editor/bezier_view.nim b/nimx/editor/bezier_view.nim index fa421f337..56c263994 100644 --- a/nimx/editor/bezier_view.nim +++ b/nimx/editor/bezier_view.nim @@ -51,14 +51,14 @@ method onTouchEv*(v: BezierView, e: var Event): bool = result = true -method init*(v: BezierView, r: Rect)= - procCall v.View.init(r) +method init*(v: BezierView, gfx: GraphicsContext, r: Rect)= + procCall v.View.init(gfx, r) v.backgroundColor = newColor(0.7, 0.7, 0.7, 1.0) method draw*(v: BezierView, r: Rect) = procCall v.View.draw(r) - let c = currentContext() + template c: untyped = v.gfx let botLeft = newPoint(0, v.bounds.height) let topRight = newPoint(v.bounds.width, 0.0) diff --git a/nimx/editor/edit_view.nim b/nimx/editor/edit_view.nim index df17373af..ea9138659 100644 --- a/nimx/editor/edit_view.nim +++ b/nimx/editor/edit_view.nim @@ -1,7 +1,7 @@ import times, json, math import nimx / [view, panel_view, context, undo_manager, toolbar, button, menu, inspector_panel, - gesture_detector, window_event_handling, view_event_handling, abstract_window, + gesture_detector, window_event_handling, view_event_handling, window, serializers, key_commands, ui_resource, private/async] import nimx/property_editors/[autoresizing_mask_editor, standard_editors] # Imported here to be registered in the propedit registry @@ -66,7 +66,7 @@ method onKeyDown(v: EventCatchingView, e : var Event): bool = echo jn let s = newJsonDeserializer(parseJson(pbi.data)) var nv: View - s.deserialize(nv) + s.deserialize(nv, v.gfx) doAssert(not nv.isNil) var targetView = v.selectedView if targetView.isNil: @@ -76,7 +76,7 @@ method onKeyDown(v: EventCatchingView, e : var Event): bool = do(): nv.removeFromSuperview() of kcOpen: - v.editor.document.open() + v.editor.document.open(v.gfx) of kcSave: v.editor.document.save() of kcSaveAs: @@ -103,7 +103,7 @@ proc endEditing*(e: Editor) = e.eventCatchingView.removeFromSuperview() proc setupNewViewButton(e:Editor, b: Button) = - # let b = Button.new(newRect(0, 30, 120, 20)) + # let b = Button.new(w.gfx, newRect(0, 30, 120, 20)) # b.title = "New view" b.onAction do(): var menu : Menu @@ -115,10 +115,10 @@ proc setupNewViewButton(e:Editor, b: Button) = menuItem.action = proc() = let v = View(newObjectOfClass(menuItem.title)) if not e.eventCatchingView.selectedView.isNil: - v.init(newRect(10, 10, 100, 100)) + v.init(e.workspace.gfx, newRect(10, 10, 100, 100)) e.eventCatchingView.selectedView.addSubview(v) else: - v.init(newRect(200, 200, 100, 100)) + v.init(e.workspace.gfx, newRect(200, 200, 100, 100)) e.document.view.addSubview(v) e.eventCatchingView.selectedView = v v.name = e.document.defaultName(menuItem.title) @@ -132,7 +132,7 @@ when savingAndLoadingEnabled: proc setupLoadButton(e: Editor, b: Button) = b.onAction do(): e.selectedView = nil - e.document.open() + e.document.open(e.workspace.gfx) proc setupSaveButton(e: Editor, b: Button) = b.onAction do(): @@ -142,22 +142,18 @@ proc setupSimulateButton(e: Editor, b: Button)= b.onAction do(): var simulateWnd = newWindow(newRect(100, 100, e.document.view.bounds.width, e.document.view.bounds.height)) simulateWnd.title = "Simulate" - simulateWnd.addSubview( - deserializeView( - e.document.serializeView() - ) - ) + simulateWnd.addSubview(deserializeView(e.document.serializeView(), simulateWnd.gfx)) echo "simulate" proc startNimxEditorAsync*(wnd: Window) {.async.}= var editor = new(Editor) - editor.workspace = new(EditorWorkspace, wnd.bounds) + editor.workspace = new(EditorWorkspace, wnd.gfx, wnd.bounds) editor.workspace.autoresizingMask = {afFlexibleWidth, afFlexibleHeight} wnd.addSubview(editor.workspace) - editor.eventCatchingView = EventCatchingView.new(wnd.bounds) + editor.eventCatchingView = EventCatchingView.new(wnd.gfx, wnd.bounds) editor.eventCatchingView.editor = editor editor.eventCatchingView.autoresizingMask = {afFlexibleWidth, afFlexibleHeight} wnd.addSubview(editor.eventCatchingView) @@ -165,7 +161,7 @@ proc startNimxEditorAsync*(wnd: Window) {.async.}= editor.document = newUIDocument(editor) editor.workspace.addSubview(editor.document.view) - var ui = await loadUiResourceAsync("assets/top_panel.nimx") + var ui = await loadUiResourceAsync("assets/top_panel.nimx", wnd.gfx) var topPanel = ui.view topPanel.setFrameOrigin(zeroPoint) wnd.addSubview(topPanel) @@ -182,11 +178,12 @@ proc startNimxEditorAsync*(wnd: Window) {.async.}= ui.getView(View, "gridSize").initPropertyEditor(editor.eventCatchingView, "gridSize", editor.eventCatchingView.gridSize) - editor.inspector = InspectorPanel.new(newRect(0, 0, 300, 600)) + var propWnd = newWindow(newRect(680, 100, 300, 600)) + + editor.inspector = InspectorPanel.new(propWnd.gfx, newRect(0, 0, 300, 600)) editor.inspector.onPropertyChanged do(name: string): wnd.setNeedsDisplay() - var propWnd = newWindow(newRect(680, 100, 300, 600)) propWnd.title = "Inspector" editor.inspector.autoresizingMask = {afFlexibleWidth, afFlexibleHeight} @@ -323,7 +320,7 @@ method onTouchEv*(v: EventCatchingView, e: var Event): bool = result = true proc drawSelectionRect(v: EventCatchingView) = - let c = currentContext() + template c: untyped = v.gfx c.fillColor = clearColor() c.strokeColor = newGrayColor(0.3) c.strokeWidth = 1 diff --git a/nimx/editor/editor_workspace.nim b/nimx/editor/editor_workspace.nim index a7f1413f6..54d8d7500 100644 --- a/nimx/editor/editor_workspace.nim +++ b/nimx/editor/editor_workspace.nim @@ -6,6 +6,6 @@ method draw*(v: EditorWorkspace, r: Rect)= procCall v.View.draw(r) if v.gridSize != zeroSize: - drawGrid(v.bounds, v.gridSize) + drawGrid(v.gfx, v.bounds, v.gridSize) diff --git a/nimx/editor/grid_drawing.nim b/nimx/editor/grid_drawing.nim index e55a1c9c2..4789a5952 100644 --- a/nimx/editor/grid_drawing.nim +++ b/nimx/editor/grid_drawing.nim @@ -1,12 +1,9 @@ {.used.} -import nimx/context -import nimx/types +import nimx / [ context, types ] -proc drawGrid*(bounds: Rect, gridSize: Size, shift = zeroSize) = - let c = currentContext() +proc drawGrid*(c: GraphicsContext, bounds: Rect, gridSize: Size, shift = zeroSize) = c.strokeWidth = 0 c.strokeColor = blackColor() - c.fillColor = blackColor() var r = newRect(0, 0, 1, bounds.height) diff --git a/nimx/editor/tab_view.nim b/nimx/editor/tab_view.nim index 89f77eeb2..3a1ec8ab0 100644 --- a/nimx/editor/tab_view.nim +++ b/nimx/editor/tab_view.nim @@ -50,8 +50,8 @@ proc updateSubviewConstraintProtos(v: TabView) = orientationOption(left, v.tabBarThickness) orientationOption(right, -v.tabBarThickness) -method init*(v: TabView, r: Rect) = - procCall v.View.init(r) +method init*(v: TabView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.tabs = @[] v.tabBarThickness = 25 v.mTabBarOrientation = TabBarOrientation.top @@ -182,7 +182,7 @@ proc removeFromSplitViewSystem*(v: View) proc userConfigurable*(v: TabView): bool = not v.configurationButton.isNil proc `userConfigurable=`*(v: TabView, b: bool) = if b and v.configurationButton.isNil: - v.configurationButton = Button.new(newRect(0, 0, 15, 15)) + v.configurationButton = Button.new(v.gfx, newRect(0, 0, 15, 15)) v.configurationButton.title = "c" v.updateConfigurationButtonLayout() v.configurationButton.onAction do(): @@ -214,7 +214,10 @@ proc `userConfigurable=`*(v: TabView, b: bool) = v.configurationButton = nil proc updateTabFrames(v: TabView) = - let f = systemFont() + template gfx: untyped = v.gfx + template fontCtx: untyped = gfx.fontCtx + template gl: untyped = gfx.gl + let f = systemFont(fontCtx) case v.tabBarOrientation of TabBarOrientation.top, TabBarOrientation.bottom: var top = 0.Coord @@ -226,7 +229,7 @@ proc updateTabFrames(v: TabView) = v.tabs[i].frame.origin.x = 0 else: v.tabs[i].frame.origin.x = v.tabs[i - 1].frame.maxX - v.tabs[i].frame.size.width = f.sizeOfString(v.tabs[i].title).width + 10 + v.tabs[i].frame.size.width = sizeOfString(fontCtx, gl, f, v.tabs[i].title).width + 10 v.tabs[i].frame.size.height = v.tabBarThickness of TabBarOrientation.left, TabBarOrientation.right: var left = 0.Coord @@ -238,7 +241,7 @@ proc updateTabFrames(v: TabView) = v.tabs[i].frame.origin.y = 0 else: v.tabs[i].frame.origin.y = v.tabs[i - 1].frame.maxY - v.tabs[i].frame.size.height = f.sizeOfString(v.tabs[i].title).width + 10 + v.tabs[i].frame.size.height = sizeOfString(fontCtx, gl, f, v.tabs[i].title).width + 10 v.tabs[i].frame.size.width = v.tabBarThickness proc tabsCount*(v: TabView): int = v.tabs.len @@ -253,27 +256,29 @@ template updateTabFramesIfNeeded(v: TabView) = v.updateTabFrames() method draw*(v: TabView, r: Rect) = + template gfx: untyped = v.gfx + template fontCtx: untyped = gfx.fontCtx + template gl: untyped = gfx.gl procCall v.View.draw(r) v.updateTabFramesIfNeeded() - let c = currentContext() - let f = systemFont() + let f = systemFont(fontCtx) for i in 0 .. v.tabs.high: let t = v.tabs[i].title if i == v.selectedTab: - c.fillColor = newGrayColor(0.2) - c.drawRect(v.tabs[i].frame) - c.fillColor = newGrayColor(0.8) + gfx.fillColor = newGrayColor(0.2) + gfx.drawRect(v.tabs[i].frame) + gfx.fillColor = newGrayColor(0.8) else: - c.fillColor = blackColor() + gfx.fillColor = blackColor() if v.tabBarOrientation == TabBarOrientation.left or v.tabBarOrientation == TabBarOrientation.right: - var tmpTransform = c.transform + var tmpTransform = gfx.transform tmpTransform.translate(newVector3(v.tabs[i].frame.x + v.tabBarThickness, v.tabs[i].frame.y)) tmpTransform.rotateZ(PI/2) - c.withTransform tmpTransform: - c.drawText(f, newPoint(5, 5), t) + gfx.withTransform tmpTransform: + gfx.drawText(f, newPoint(5, 5), t) else: - c.drawText(f, newPoint(v.tabs[i].frame.x + 5, v.tabs[i].frame.y + 5), t) + gfx.drawText(f, newPoint(v.tabs[i].frame.x + 5, v.tabs[i].frame.y + 5), t) proc tabBarRect(v: TabView): Rect = result = v.bounds @@ -311,33 +316,35 @@ proc findSubviewOfTypeAtPoint[T](v: View, p: Point): T = result = findSubviewOfTypeAtPointAux[T](v, p) if not result.isNil and View(result) == v: result = nil -proc newDraggingView(tab: Tab): TabDraggingView = - result = TabDraggingView.new(newRect(0, 0, 400, 400)) +proc newDraggingView(gfx: GraphicsContext, tab: Tab): TabDraggingView = + result = TabDraggingView.new(gfx, newRect(0, 0, 400, 400)) result.title = tab.title tab.view.setFrame(newRect(0, 25, 400, 400 - 25)) result.addSubview(tab.view) method draw(v: TabDraggingView, r: Rect) = + template gfx: untyped = v.gfx + template fontCtx: untyped = gfx.fontCtx + template gl: untyped = gfx.gl procCall v.View.draw(r) - let c = currentContext() - let f = systemFont() + let f = systemFont(fontCtx) var titleRect: Rect - titleRect.size.width = f.sizeOfString(v.title).width + 10 + titleRect.size.width = sizeOfString(fontCtx, gl, f, v.title).width + 10 titleRect.size.height = 25 - c.fillColor = newColor(0.4, 0.4, 0.4) - c.drawRect(titleRect) - c.fillColor = newColor(1, 0, 0) - c.drawText(f, newPoint(5, 0), v.title) + gfx.fillColor = newColor(0.4, 0.4, 0.4) + gfx.drawRect(titleRect) + gfx.fillColor = newColor(1, 0, 0) + gfx.drawText(f, newPoint(5, 0), v.title) -proc newTabViewForSplit(sz: Size, tab: Tab, prototype: TabView): TabView = - result = TabView.new(newRect(zeroPoint, sz)) +proc newTabViewForSplit(gfx: GraphicsContext, sz: Size, tab: Tab, prototype: TabView): TabView = + result = TabView.new(gfx, newRect(zeroPoint, sz)) result.addTab(tab.title, tab.view) result.dockingTabs = prototype.dockingTabs result.tabBarOrientation = prototype.tabBarOrientation result.userConfigurable = prototype.userConfigurable -proc newSplitView(r: Rect, horizontal: bool): LinearLayout = - result = LinearLayout.new(r) +proc newSplitView(gfx: GraphicsContext, r: Rect, horizontal: bool): LinearLayout = + result = LinearLayout.new(gfx, r) result.horizontal = horizontal result.autoresizingMask = {afFlexibleWidth, afFlexibleHeight} result.userResizeable = true @@ -358,7 +365,7 @@ proc splitNew(v: TabView, horizontally, before: bool, tab: Tab) = sz.height /= 2 dividerPos = sz.height - let ntv = newTabViewForSplit(zeroSize, tab, v) + let ntv = newTabViewForSplit(v.gfx, zeroSize, tab, v) ntv.onSplit = v.onSplit ntv.onRemove = v.onRemove ntv.onClose = v.onClose @@ -372,7 +379,7 @@ proc splitNew(v: TabView, horizontally, before: bool, tab: Tab) = s.insertSubviewAfter(ntv, v) else: - let vl = SplitView.new(zeroRect) + let vl = SplitView.new(v.gfx, zeroRect) vl.vertical = not horizontally vl.addConstraints(v.constraints) s.replaceSubview(v, vl) @@ -394,7 +401,7 @@ proc splitOld(v: TabView, horizontally, before: bool, tab: Tab) = else: sz.height /= 2 - let ntv = newTabViewForSplit(sz, tab, v) + let ntv = newTabViewForSplit(v.gfx, sz, tab, v) ntv.onSplit = v.onSplit ntv.onRemove = v.onRemove ntv.onClose = v.onClose @@ -409,7 +416,7 @@ proc splitOld(v: TabView, horizontally, before: bool, tab: Tab) = s.insertSubviewAfter(ntv, v) else: let fr = v.frame - let vl = newSplitView(fr, horizontally) + let vl = newSplitView(v.gfx, fr, horizontally) s.replaceSubview(v, vl) v.setFrameSize(sz) if before: @@ -438,7 +445,7 @@ proc trackDocking(v: TabView, tab: int): proc(e: Event): bool = var tabOwner = v var indexOfTabInOwner = tab var draggingView: View - var dropOverlayView = View.new(zeroRect) + var dropOverlayView = View.new(v.gfx, zeroRect) dropOverlayView.backgroundColor = newColor(0, 0, 1, 0.5) const margin = 50 @@ -496,7 +503,7 @@ proc trackDocking(v: TabView, tab: int): proc(e: Event): bool = draggingView = nil elif tabOwner.isNil and draggingView.isNil: # Create dragging view - draggingView = newDraggingView(trackedTab) + draggingView = newDraggingView(v.gfx, trackedTab) e.window.addSubview(draggingView) if not draggingView.isNil: diff --git a/nimx/editor/ui_document.nim b/nimx/editor/ui_document.nim index eb4b22c1d..7b301dc13 100644 --- a/nimx/editor/ui_document.nim +++ b/nimx/editor/ui_document.nim @@ -14,7 +14,7 @@ when savingAndLoadingEnabled: proc newUIDocument*(e: Editor): UIDocument = result.new() result.undoManager = newUndoManager() - result.view = new(View, e.workspace.bounds) + result.view = new(View, e.workspace.gfx, e.workspace.bounds) result.view.autoResizingMask = {afFlexibleWidth, afFlexibleHeight} proc defaultName*(ui: UIDocument, className: string): string = @@ -74,23 +74,23 @@ when savingAndLoadingEnabled: d.save() - proc loadViewToEditAsync(path: string): Future[View] = + proc loadViewToEditAsync(path: string, gfx: GraphicsContext): Future[View] = when defined js: newPromise() do (resolve: proc(response: View)): loadAsset[JsonNode]("file://" & path) do(jn: JsonNode, err: string): - resolve(deserializeView(jn)) + resolve(deserializeView(jn, gfx)) else: var r = newFuture[View]() loadAsset[JsonNode]("file://" & path) do(jn: JsonNode, err: string): - r.complete(deserializeView(jn)) + r.complete(deserializeView(jn, gfx)) result = r - proc loadFromPath*(d: UIDocument, path: string) {.async.}= + proc loadFromPath*(d: UIDocument, path: string, gfx: GraphicsContext) {.async.}= d.path = path if d.path.len > 0: let superview = d.view.superview d.view.removeFromSuperview() - d.view = await loadViewToEditAsync(path) + d.view = await loadViewToEditAsync(path, gfx) doAssert(not d.view.isNil) if not superview.isNil: @@ -108,7 +108,7 @@ when savingAndLoadingEnabled: superview.addSubview(d.view) - proc open*(d: UIDocument) = + proc open*(d: UIDocument, gfx: GraphicsContext) = var di: DialogInfo di.extension = "nimx" di.kind = dkOpenFile @@ -117,4 +117,4 @@ when savingAndLoadingEnabled: var path = di.show() if path.len > 0: - asyncCheck d.loadFromPath(path) + asyncCheck d.loadFromPath(path, gfx) diff --git a/nimx/expanding_view.nim b/nimx/expanding_view.nim index b22ab4671..d43759485 100644 --- a/nimx/expanding_view.nim +++ b/nimx/expanding_view.nim @@ -47,21 +47,21 @@ proc updateFrame(v: ExpandingView) = elif not v.contentView.superview.isNil: v.contentView.removeFromSuperView() -proc init*(v: ExpandingView, r: Rect, hasOffset: bool) = - procCall v.View.init(r) +proc init*(v: ExpandingView, gfx: GraphicsContext, r: Rect, hasOffset: bool) = + procCall v.View.init(gfx, r) v.backgroundColor = newColor(0.2, 0.2, 0.2, 1.0) v.title = "Expanded View" v.hasOffset = hasOffset if v.hasOffset: - v.contentView = newStackView(newRect(expandButtonSize, titleSize, r.width - expandButtonSize, r.height - titleSize)) + v.contentView = newStackView(gfx, newRect(expandButtonSize, titleSize, r.width - expandButtonSize, r.height - titleSize)) else: - v.contentView = newStackView(newRect(0, titleSize, r.width, r.height - titleSize)) + v.contentView = newStackView(gfx, newRect(0, titleSize, r.width, r.height - titleSize)) v.contentView.name = "contentView" v.contentView.resizingMask = "wb" v.addSubview(v.contentView) - v.expandBut = newExpandButton(v, newRect(0.0, 0.0, expandButtonSize, expandButtonSize)) + v.expandBut = newExpandButton(v, gfx, newRect(0.0, 0.0, expandButtonSize, expandButtonSize)) v.expandBut.onExpandAction = proc(state: bool) = v.expanded = state v.updateFrame() @@ -75,25 +75,26 @@ proc expand*(v: ExpandingView) = v.updateFrame() v.expandBut.expanded = true -proc newExpandingView*(r: Rect, hasOffset: bool = false): ExpandingView = +proc newExpandingView*(gfx: GraphicsContext, r: Rect, hasOffset: bool = false): ExpandingView = result.new() - result.init(r, hasOffset) + result.init(gfx, r, hasOffset) result.name = "expandingView" method draw(v: ExpandingView, r: Rect) = + template gfx: untyped = v.gfx + template fontCtx: untyped = gfx.fontCtx procCall v.View.draw(r) # title - let c = currentContext() var titleRect: Rect titleRect.size.width = r.width titleRect.size.height = titleSize - c.fillColor = v.titleBarColor - c.drawRect(titleRect) + gfx.fillColor = v.titleBarColor + gfx.drawRect(titleRect) - c.fillColor = v.titleTextColor - c.drawText(systemFontOfSize(14.0), newPoint(25, 1), v.title) + gfx.fillColor = v.titleTextColor + gfx.drawText(systemFontOfSize(fontCtx, 14.0), newPoint(25, 1), v.title) v.contentView.hidden = not v.expanded diff --git a/nimx/font.nim b/nimx/font.nim index 46a5c1748..448aa31ad 100644 --- a/nimx/font.nim +++ b/nimx/font.nim @@ -1,15 +1,15 @@ import unicode, streams, logging -import nimx / [ types, timer, portable_gl ] -import nimx/private/font/font_data +import types, timer, portable_gl +import private/font/font_data when defined(js): import private/font/js_glyph_provider type GlyphProvider = JsGlyphProvider else: + import private/font/fontconfig import private/font/stb_ttf_glyph_provider type GlyphProvider = StbTtfGlyphProvider - import os import ttf/edtaa3func @@ -18,22 +18,55 @@ import private/simple_table when defined(android): import nimx/assets/url_stream -type Baseline* = enum - bTop - bAlphabetic - bBottom - -type CharInfo = ref object - data: GlyphData - texture: TextureRef - -template bakedChars(ci: CharInfo): GlyphMetrics = ci.data.glyphMetrics +const dumpDebugBitmaps = false when defined(js): type FastString = cstring else: type FastString = string +type + CharInfo = ref object + data*: GlyphData + texture*: TextureRef + + FontImpl = ref object + chars: SimpleTable[int32, CharInfo] + glyphProvider: GlyphProvider + ascent: float32 + descent: float32 + + Baseline* = enum + bTop + bAlphabetic + bBottom + + Font* = ref object + impl: FontImpl + mSize: float + isHorizontal*: bool + scale*: float + filePath: string + face*: string + horizontalSpacing*: Coord + shadowX*, shadowY*, shadowBlur*: float32 + glyphMargin: int32 + baseline*: Baseline # Beware! Experinmetal! + + FontContext* = ref object + dfCtx*: DistanceFieldContext[float32] + chunksToGen*: seq[CharInfo] + sysFont*: Font + glyphGenerationTimer*: Timer + fontCache*: SimpleTable[FastString, FontImpl] + enableSubpixelDrawing*: bool + +func newFontContext*: FontContext = + result.new() + result.enableSubpixelDrawing = true + +template bakedChars(ci: CharInfo): GlyphMetrics = + ci.data.glyphMetrics template charHeightForSize(s: float): float = #if s > 128: 128 @@ -43,20 +76,12 @@ template charHeightForSize(s: float): float = template scaleForSize(s: float): float = s / charHeightForSize(s) -type FontImpl = ref object - chars: SimpleTable[int32, CharInfo] - glyphProvider: GlyphProvider - ascent: float32 - descent: float32 - -var fontCache : SimpleTable[FastString, FontImpl] - -proc cachedImplForFont(face: string, sz: float): FontImpl = - if fontCache.isNil: - fontCache = newSimpleTable(FastString, FontImpl) +proc cachedImplForFont(fontCtx: FontContext, face: string, sz: float): FontImpl = + if fontCtx.fontCache.isNil: + fontCtx.fontCache = newSimpleTable(FastString, FontImpl) var key : FastString = face & "_" & $charHeightForSize(sz).int - if fontCache.hasKey(key): - result = fontCache[key] + if fontCtx.fontCache.hasKey(key): + result = fontCtx.fontCache[key] else: result.new() result.chars = newSimpleTable(int32, CharInfo) @@ -67,29 +92,15 @@ proc cachedImplForFont(face: string, sz: float): FontImpl = result.glyphProvider.setPath(face) result.glyphProvider.setSize(charHeightForSize(sz)) result.glyphProvider.glyphMargin = 8 - fontCache[key] = result - -type Font* = ref object - impl: FontImpl - mSize: float - isHorizontal*: bool - scale*: float - filePath: string - face*: string - horizontalSpacing*: Coord - shadowX*, shadowY*, shadowBlur*: float32 - glyphMargin: int32 - baseline*: Baseline # Beware! Experinmetal! - -proc `size=`*(f: Font, s: float) = + fontCtx.fontCache[key] = result + +proc `size=`*(f: Font, fontCtx: FontContext, s: float) = f.mSize = s f.scale = scaleForSize(s) - f.impl = cachedImplForFont(f.filePath, s) + f.impl = cachedImplForFont(fontCtx, f.filePath, s) template size*(f: Font): float = f.mSize -const dumpDebugBitmaps = false - when dumpDebugBitmaps: when defined(js): proc logBitmap(title: cstring, bytes: openarray[byte], width, height: int) = @@ -142,16 +153,14 @@ proc bakeChars(f: Font, start: int32, res: CharInfo) = dumpBitmaps("df", res.data.bitmap, res.data.bitmapWidth, res.data.bitmapHeight, start, fSize) when not defined(js): - proc newFontWithFile*(pathToTTFile: string, size: float): Font = + proc newFontWithFile*(fontCtx: FontContext, pathToTTFile: string, size: float): Font = result.new() result.isHorizontal = true # TODO: Support vertical fonts result.filePath = pathToTTFile result.face = splitFile(pathToTTFile).name - result.size = size + `size=`(result, fontCtx, size) result.glyphMargin = 8 -var sysFont : Font - const preferredFonts = when defined(js) or defined(windows) or defined(emscripten) or defined(wasm): [ "Arial", @@ -176,9 +185,6 @@ const preferredFonts = when defined(js) or defined(windows) or defined(emscripte "DejaVuSans" ] -when not defined(js): - import nimx/private/font/fontconfig - proc getAvailableFonts*(isSystem: bool = false): seq[string] = result = newSeq[string]() when not defined(js) and not defined(android) and not defined(ios): @@ -189,18 +195,18 @@ proc getAvailableFonts*(isSystem: bool = false): seq[string] = for f in walkFiles(getAppDir() / "*.ttf"): result.add(splitFile(f).name) -proc newFontWithFace*(face: string, size: float): Font = +proc newFontWithFace*(fontCtx: FontContext, face: string, size: float): Font = when defined(js): result.new() result.filePath = face result.face = face result.isHorizontal = true # TODO: Support vertical fonts - result.size = size + `size=`(result, fontCtx, size) result.glyphMargin = 8 else: let path = findFontFileForFace(face) if path.len != 0: - result = newFontWithFile(path, size) + result = newFontWithFile(fontCtx, path, size) else: when defined(android): let path = face & ".ttf" @@ -210,20 +216,20 @@ proc newFontWithFace*(face: string, size: float): Font = s = st if not s.isNil: s.close() - result = newFontWithFile(url, size) + result = newFontWithFile(fontCtx, url, size) proc systemFontSize*(): float = 16 -proc setGlyphMargin*(f: Font, margin: int32) {.deprecated.} = +proc setGlyphMargin*(fontCtx: FontContext, f: Font, margin: int32) {.deprecated.} = if margin == f.glyphMargin: return f.glyphMargin = margin - f.impl = cachedImplForFont(f.filePath, f.size) + f.impl = cachedImplForFont(fontCtx, f.filePath, f.size) -proc systemFontOfSize*(size: float): Font = +proc systemFontOfSize*(fontCtx: FontContext, size: float): Font = for f in preferredFonts: - result = newFontWithFace(f, size) + result = newFontWithFace(fontCtx, f, size) if result != nil: return when not defined(js): @@ -232,18 +238,16 @@ proc systemFontOfSize*(size: float): Font = for f in potentialFontFilesForFace(face): error "Tried path '", f, "'" -proc systemFont*(): Font = - if sysFont == nil: - sysFont = systemFontOfSize(systemFontSize()) - result = sysFont +proc systemFont*(fontCtx: FontContext): Font = + if fontCtx.sysFont == nil: + fontCtx.sysFont = systemFontOfSize(fontCtx, systemFontSize()) + result = fontCtx.sysFont if result == nil: warn "Could not create system font" -var dfCtx : DistanceFieldContext[float32] - -proc generateDistanceFieldForGlyph(ch: CharInfo, index: int, uploadToTexture: bool) = - if dfCtx.isNil: - dfCtx = newDistanceFieldContext() +proc generateDistanceFieldForGlyph(fontCtx: FontContext, gl: GL, ch: CharInfo, index: int, uploadToTexture: bool) = + if fontCtx.dfCtx.isNil: + fontCtx.dfCtx = newDistanceFieldContext() let c = charOff(index) let glyphMargin = 8 @@ -253,41 +257,37 @@ proc generateDistanceFieldForGlyph(ch: CharInfo, index: int, uploadToTexture: bo let w = ch.bakedChars.charOffComp(c, compWidth).cint + glyphMargin * 2 let h = ch.bakedChars.charOffComp(c, compHeight).cint + glyphMargin * 2 - dfCtx.make_distance_map(ch.data.bitmap, x, y, w, h, ch.data.bitmapWidth.int, not uploadToTexture) + fontCtx.dfCtx.make_distance_map(ch.data.bitmap, x, y, w, h, ch.data.bitmapWidth.int, not uploadToTexture) if uploadToTexture: - let gl = sharedGL() gl.bindTexture(gl.TEXTURE_2D, ch.texture) if w mod 4 == 0: - gl.texSubImage2D(gl.TEXTURE_2D, 0, GLint(x), GLint(y), GLsizei(w), GLsizei(h), gl.ALPHA, gl.UNSIGNED_BYTE, dfCtx.output) + gl.texSubImage2D(gl.TEXTURE_2D, 0, GLint(x), GLint(y), GLsizei(w), GLsizei(h), gl.ALPHA, gl.UNSIGNED_BYTE, fontCtx.dfCtx.output) else: gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1) - gl.texSubImage2D(gl.TEXTURE_2D, 0, GLint(x), GLint(y), GLsizei(w), GLsizei(h), gl.ALPHA, gl.UNSIGNED_BYTE, dfCtx.output) + gl.texSubImage2D(gl.TEXTURE_2D, 0, GLint(x), GLint(y), GLsizei(w), GLsizei(h), gl.ALPHA, gl.UNSIGNED_BYTE, fontCtx.dfCtx.output) gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4) ch.data.dfDoneForGlyph[index] = true when dumpDebugBitmaps: - if not dfCtx.output.isNil: - dumpBitmaps("df" & $index, dfCtx.output, w, h, 0, 555.0) - -var glyphGenerationTimer: Timer -var chunksToGen = newSeq[CharInfo]() + if not fontCtx.dfCtx.output.isNil: + dumpBitmaps("df" & $index, fontCtx.dfCtx.output, w, h, 0, 555.0) -proc generateDistanceFields() = - let ch = chunksToGen[^1] +proc generateDistanceFields(fontCtx: FontContext, gl: GL) = + let ch = fontCtx.chunksToGen[^1] if ch.data.dfDoneForGlyph.len != 0: for i in 0 ..< charChunkLength: if not ch.data.dfDoneForGlyph[i]: - generateDistanceFieldForGlyph(ch, i, true) + generateDistanceFieldForGlyph(fontCtx, gl, ch, i, true) return - chunksToGen.setLen(chunksToGen.len - 1) + fontCtx.chunksToGen.setLen(fontCtx.chunksToGen.len - 1) ch.data.bitmap.setLen(0) ch.data.dfDoneForGlyph.setLen(0) - if chunksToGen.len == 0: - glyphGenerationTimer.clear() - glyphGenerationTimer = nil - dfCtx = nil + if fontCtx.chunksToGen.len == 0: + fontCtx.glyphGenerationTimer.clear() + fontCtx.glyphGenerationTimer = nil + fontCtx.dfCtx = nil -proc chunkAndCharIndexForRune(f: Font, r: Rune): tuple[ch: CharInfo, index: int] = +proc chunkAndCharIndexForRune(fontCtx: FontContext, gl: GL, f: Font, r: Rune): tuple[ch: CharInfo, index: int] = let chunkStart = r.int32 div charChunkLength result.index = r.int mod charChunkLength var ch: CharInfo @@ -301,15 +301,14 @@ proc chunkAndCharIndexForRune(f: Font, r: Rune): tuple[ch: CharInfo, index: int] result.ch = ch - let gl = sharedGL() if not gl.isNil: if ch.texture.isEmpty: if not ch.data.dfDoneForGlyph[result.index]: - generateDistanceFieldForGlyph(ch, result.index, false) + generateDistanceFieldForGlyph(fontCtx, gl, ch, result.index, false) - chunksToGen.add(ch) - if glyphGenerationTimer.isNil: - glyphGenerationTimer = setInterval(0.1, generateDistanceFields) + fontCtx.chunksToGen.add(ch) + if fontCtx.glyphGenerationTimer.isNil: + fontCtx.glyphGenerationTimer = setInterval(0.1, proc() = generateDistanceFields(fontCtx, gl)) ch.texture = gl.createTexture() gl.bindTexture(gl.TEXTURE_2D, ch.texture) @@ -322,10 +321,10 @@ proc chunkAndCharIndexForRune(f: Font, r: Rune): tuple[ch: CharInfo, index: int] #gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST) #gl.generateMipmap(gl.TEXTURE_2D) elif ch.data.dfDoneForGlyph.len != 0 and not ch.data.dfDoneForGlyph[result.index]: - generateDistanceFieldForGlyph(ch, result.index, true) + generateDistanceFieldForGlyph(fontCtx, gl, ch, result.index, true) -proc getQuadDataForRune*(f: Font, r: Rune, quad: var openarray[Coord], offset: int, texture: var TextureRef, pt: var Point) = - let (chunk, charIndexInChunk) = f.chunkAndCharIndexForRune(r) +proc getQuadDataForRune*(fontCtx: FontContext, gl: GL, f: Font, r: Rune, quad: var openarray[Coord], offset: int, texture: var TextureRef, pt: var Point) = + let (chunk, charIndexInChunk) = chunkAndCharIndexForRune(fontCtx, gl, f, r) let c = charOff(charIndexInChunk) template charComp(e: GlyphMetricsComponent): auto = @@ -361,18 +360,18 @@ proc getQuadDataForRune*(f: Font, r: Rune, quad: var openarray[Coord], offset: i pt.x += charComp(compAdvance) * f.scale texture = chunk.texture -proc getCharComponent*(f: Font, text: string, comp: GlyphMetricsComponent): Coord = - let (chunk, charIndexInChunk) = f.chunkAndCharIndexForRune(text.runeAtPos(0)) +proc getCharComponent*(fontCtx: FontContext, gl: GL, f: Font, text: string, comp: GlyphMetricsComponent): Coord = + let (chunk, charIndexInChunk) = chunkAndCharIndexForRune(fontCtx, gl, f, text.runeAtPos(0)) let c = charOff(charIndexInChunk) f.updateFontMetricsIfNeeded() result = chunk.data.glyphMetrics.charOffComp(c, comp).Coord -template getQuadDataForRune*(f: Font, r: Rune, quad: var array[16, Coord], texture: var TextureRef, pt: var Point) = - f.getQuadDataForRune(r, quad, 0, texture, pt) +template getQuadDataForRune*(fontCtx: FontContext, f: Font, r: Rune, quad: var array[16, Coord], texture: var TextureRef, pt: var Point) = + getQuadDataForRune(fontCtx, f, r, quad, 0, texture, pt) -proc getAdvanceForRune*(f: Font, r: Rune): Coord = - let (chunk, charIndexInChunk) = f.chunkAndCharIndexForRune(r) +proc getAdvanceForRune*(fontCtx: FontContext, gl: GL, f: Font, r: Rune): Coord = + let (chunk, charIndexInChunk) = chunkAndCharIndexForRune(fontCtx, gl, f, r) let c = charOff(charIndexInChunk) result = chunk.bakedChars.charOffComp(c, compAdvance).Coord * f.scale @@ -380,7 +379,7 @@ proc height*(f: Font): float32 = f.updateFontMetricsIfNeeded() result = (f.impl.ascent - f.impl.descent) * f.scale -proc sizeOfString*(f: Font, s: string): Size = +proc sizeOfString*(fontCtx: FontContext, gl: GL, f: Font, s: string): Size = var pt : Point var first = true for ch in s.runes: @@ -388,17 +387,17 @@ proc sizeOfString*(f: Font, s: string): Size = first = false else: pt.x += f.horizontalSpacing - pt.x += f.getAdvanceForRune(ch) + pt.x += getAdvanceForRune(fontCtx, gl, f, ch) result = newSize(pt.x, f.height) -proc getClosestCursorPositionToPointInString*(f: Font, s: string, p: Point, position: var int, offset: var Coord) {.deprecated.} = +proc getClosestCursorPositionToPointInString*(fontCtx: FontContext, gl: GL, f: Font, s: string, p: Point, position: var int, offset: var Coord) {.deprecated.} = var pt = zeroPoint var closestPoint = zeroPoint var quad: array[16, Coord] var i = 0 var tex: TextureRef for ch in s.runes: - f.getQuadDataForRune(ch, quad, tex, pt) + getQuadDataForRune(fontCtx, gl, f, ch, quad, i, tex, pt) if (f.isHorizontal and (abs(p.x - pt.x) < abs(p.x - closestPoint.x))) or (not f.isHorizontal and (abs(p.y - pt.y) < abs(p.y - closestPoint.y))): closestPoint = pt @@ -408,7 +407,7 @@ proc getClosestCursorPositionToPointInString*(f: Font, s: string, p: Point, posi offset = if f.isHorizontal: closestPoint.x else: closestPoint.y if offset == 0: position = 0 -proc cursorOffsetForPositionInString*(f: Font, s: string, position: int): Coord {.deprecated.} = +proc cursorOffsetForPositionInString*(fontCtx: FontContext, gl: GL, f: Font, s: string, position: int): Coord {.deprecated.} = var pt = zeroPoint var quad: array[16, Coord] var i = 0 @@ -419,6 +418,6 @@ proc cursorOffsetForPositionInString*(f: Font, s: string, position: int): Coord break inc i - f.getQuadDataForRune(ch, quad, tex, pt) + getQuadDataForRune(fontCtx, gl, f, ch, quad, i, tex, pt) pt.x += f.horizontalSpacing result = if f.isHorizontal: pt.x else: pt.y diff --git a/nimx/form_view.nim b/nimx/form_view.nim index 556436f83..94740c449 100644 --- a/nimx/form_view.nim +++ b/nimx/form_view.nim @@ -6,19 +6,19 @@ import tables type FormView* = ref object of View labelsMap: Table[string, int] -proc newFormView*(r: Rect, numberOfFields: int, adjustFrameHeight: bool = true): FormView = +proc newFormView*(gfx: GraphicsContext, r: Rect, numberOfFields: int, adjustFrameHeight: bool = true): FormView = result.new() let fieldHeight = 20.Coord var fr = r if adjustFrameHeight: fr.size.height = fieldHeight * numberOfFields.Coord - result.init(fr) + result.init(gfx, fr) result.labelsMap = initTable[string, int]() for i in 0 .. < numberOfFields: - let label = newLabel(newRect(0, i.Coord * fieldHeight, r.width / 3, fieldHeight)) + let label = newLabel(gfx, newRect(0, i.Coord * fieldHeight, r.width / 3, fieldHeight)) result.addSubview(label) - let value = newTextField(newRect(r.width / 3, i.Coord * fieldHeight, r.width / 3 * 2, fieldHeight)) + let value = newTextField(gfx, newRect(r.width / 3, i.Coord * fieldHeight, r.width / 3 * 2, fieldHeight)) result.addSubview(value) proc labelAtIndex(v: FormView, index: int): TextField = TextField(v.subviews[index * 2]) @@ -39,8 +39,8 @@ proc setValue*(v: FormView, index: int, value: string) = proc setValue*(v: FormView, label: string, value: string) = v.inputForLabel(label).text = value -proc newFormView*(r: Rect, fieldNames: openarray[string], adjustFrameHeight: bool = true): FormView = - result = newFormView(r, fieldNames.len, adjustFrameHeight) +proc newFormView*(gfx: GraphicsContext, r: Rect, fieldNames: openarray[string], adjustFrameHeight: bool = true): FormView = + result = newFormView(gfx, r, fieldNames.len, adjustFrameHeight) for i, n in fieldNames: result.setLabel(i, n) result.setValue(i, "") diff --git a/nimx/formatted_text.nim b/nimx/formatted_text.nim index 713c09fd4..3429adc45 100644 --- a/nimx/formatted_text.nim +++ b/nimx/formatted_text.nim @@ -1,5 +1,6 @@ import unicode, algorithm, strutils, sequtils -import nimx/font, nimx/types, nimx/unistring, nimx/utils/lower_bound +import portable_gl +import nimx / [ context, font, types, unistring, utils/lower_bound ] type @@ -67,8 +68,8 @@ type haCenter haJustify -proc defaultAttributes(): Attributes = - result.font = systemFont() +proc defaultAttributes(fontCtx: FontContext): Attributes = + result.font = systemFont(fontCtx) result.textColor = blackColor() proc `text=`*(t: FormattedText, s: string) = @@ -86,7 +87,7 @@ template lineSpacing*(t: FormattedText): float32 = t.mLineSpacing proc newFormattedText*(s: string = ""): FormattedText = result.new() result.mText = s - result.mAttributes = @[defaultAttributes()] + result.mAttributes = @[] result.lines = @[] result.shadowAttrs = @[] result.strokeAttrs = @[] @@ -94,7 +95,7 @@ proc newFormattedText*(s: string = ""): FormattedText = result.mLineSpacing = 2 result.mTruncationBehavior = tbNone -proc updateCache(t: FormattedText) = +proc updateCache(fontCtx: FontContext, gl: GL, t: FormattedText) = t.cacheValid = true t.lines.setLen(0) t.shadowAttrs.setLen(0) @@ -102,6 +103,9 @@ proc updateCache(t: FormattedText) = t.mTotalHeight = 0 t.mTotalWidth = 0 + if t.mAttributes.len == 0: + t.mAttributes.add(defaultAttributes(fontCtx)) + var curLineInfo: LineInfo curLineInfo.height = t.mAttributes[0].font.height @@ -148,7 +152,7 @@ proc updateCache(t: FormattedText) = fastRuneAt(t.mText, i, c, true) - let runeWidth = font.getAdvanceForRune(c) + let runeWidth = getAdvanceForRune(fontCtx, gl, font, c) curWordWidth += runeWidth curWordHeight = max(curWordHeight, font.height) @@ -222,8 +226,8 @@ proc updateCache(t: FormattedText) = # echo "lines: ", t.lines # echo "shadow attrs: ", t.shadowAttrs -template updateCacheIfNeeded(t: FormattedText) = - if not t.cacheValid: t.updateCache() +template updateCacheIfNeeded(fontCtx: FontContext, gl: GL, t: FormattedText) = + if not t.cacheValid: updateCache(fontCtx, gl, t) proc `boundingSize=`*(t: FormattedText, s: Size) = if s != t.mBoundingSize: @@ -239,44 +243,44 @@ proc `truncationBehavior=`*(t: FormattedText, b: TruncationBehavior) = template truncationBehavior*(t: FormattedText): TruncationBehavior = t.mTruncationBehavior -proc lineOfRuneAtPos*(t: FormattedText, pos: int): int = - t.updateCacheIfNeeded() +proc lineOfRuneAtPos*(fontCtx: FontContext, gl: GL, t: FormattedText, pos: int): int = + updateCacheIfNeeded(fontCtx, gl, t) result = lowerBoundIt(t.lines, t.lines.low, t.lines.high, cmp(it.startRune, pos) <= 0) - 1 -proc lineTop*(t: FormattedText, ln: int): float32 = - t.updateCacheIfNeeded() +proc lineTop*(fontCtx: FontContext, gl: GL, t: FormattedText, ln: int): float32 = + updateCacheIfNeeded(fontCtx, gl, t) t.lines[ln].top -proc lineHeight*(t: FormattedText, ln: int): float32 = - t.updateCacheIfNeeded() +proc lineHeight*(fontCtx: FontContext, gl: GL, t: FormattedText, ln: int): float32 = + updateCacheIfNeeded(fontCtx, gl, t) t.lines[ln].height -proc lineWidth*(t: FormattedText, ln: int): float32 = - t.updateCacheIfNeeded() +proc lineWidth*(fontCtx: FontContext, gl: GL, t: FormattedText, ln: int): float32 = + updateCacheIfNeeded(fontCtx, gl, t) t.lines[ln].width -proc lineLeft*(t: FormattedText, ln: int): float32 = - t.updateCacheIfNeeded() +proc lineLeft*(fontCtx: FontContext, gl: GL, t: FormattedText, ln: int): float32 = + updateCacheIfNeeded(fontCtx, gl, t) case t.horizontalAlignment of haCenter: (t.mBoundingSize.width - t.lines[ln].width) / 2 of haRight: t.mBoundingSize.width - t.lines[ln].width else: 0 -proc lineBaseline*(t: FormattedText, ln: int): float32 = +proc lineBaseline*(fontCtx: FontContext, gl: GL, t: FormattedText, ln: int): float32 = # Do not use this! - t.updateCacheIfNeeded() + updateCacheIfNeeded(fontCtx, gl, t) result = t.lines[ln].baseline -proc hasShadow*(t: FormattedText): bool = - t.updateCacheIfNeeded() +proc hasShadow*(fontCtx: FontContext, gl: GL, t: FormattedText): bool = + updateCacheIfNeeded(fontCtx, gl, t) t.shadowAttrs.len > 0 -proc totalHeight*(t: FormattedText): float32 = - t.updateCacheIfNeeded() +proc totalHeight*(fontCtx: FontContext, gl: GL, t: FormattedText): float32 = + updateCacheIfNeeded(fontCtx, gl, t) t.mTotalHeight -proc totalWidth*(t: FormattedText): float32 = - t.updateCacheIfNeeded() +proc totalWidth*(fontCtx: FontContext, gl: GL, t: FormattedText): float32 = + updateCacheIfNeeded(fontCtx, gl, t) t.mTotalWidth proc prepareAttributes(t: FormattedText, a: int): int = @@ -304,33 +308,39 @@ iterator attrsInRange(t: FormattedText, a, b: int): int = t.prepareAttributes(b) for i in aa ..< ab: yield i -proc setFontInRange*(t: FormattedText, a, b: int, f: Font) = +proc setFontInRange*(fontCtx: FontContext, gl: GL, t: FormattedText, a, b: int, f: Font) = + updateCacheIfNeeded(fontCtx, gl, t) for i in t.attrsInRange(a, b): t.mAttributes[i].font = f t.cacheValid = false -proc setTrackingInRange*(t: FormattedText, a, b: int, v: float32) = +proc setTrackingInRange*(fontCtx: FontContext, gl: GL, t: FormattedText, a, b: int, v: float32) = + updateCacheIfNeeded(fontCtx, gl, t) for i in t.attrsInRange(a, b): t.mAttributes[i].tracking = v t.cacheValid = false -proc setTextColorInRange*(t: FormattedText, a, b: int, c: Color) = +proc setTextColorInRange*(fontCtx: FontContext, gl: GL, t: FormattedText, a, b: int, c: Color) = + updateCacheIfNeeded(fontCtx, gl, t) for i in t.attrsInRange(a, b): t.mAttributes[i].textColor = c t.mAttributes[i].isTextGradient = false -proc setTextColorInRange*(t: FormattedText, a, b: int, color1, color2: Color) = +proc setTextColorInRange*(fontCtx: FontContext, gl: GL, t: FormattedText, a, b: int, color1, color2: Color) = + updateCacheIfNeeded(fontCtx, gl, t) for i in t.attrsInRange(a, b): t.mAttributes[i].textColor = color1 t.mAttributes[i].textColor2 = color2 t.mAttributes[i].isTextGradient = true -proc setTextAlphaInRange*(t: FormattedText, a, b: int, alpha: float32) = +proc setTextAlphaInRange*(fontCtx: FontContext, gl: GL, t: FormattedText, a, b: int, alpha: float32) = + updateCacheIfNeeded(fontCtx, gl, t) for i in t.attrsInRange(a, b): t.mAttributes[i].textColor.a = alpha t.mAttributes[i].textColor2.a = alpha -proc setShadowInRange*(t: FormattedText, a, b: int, color: Color, offset: Size, radius, spread: float32) = +proc setShadowInRange*(fontCtx: FontContext, gl: GL, t: FormattedText, a, b: int, color: Color, offset: Size, radius, spread: float32) = + updateCacheIfNeeded(fontCtx, gl, t) for i in t.attrsInRange(a, b): t.mAttributes[i].shadowColor = color t.mAttributes[i].shadowOffset = offset @@ -338,14 +348,16 @@ proc setShadowInRange*(t: FormattedText, a, b: int, color: Color, offset: Size, t.mAttributes[i].shadowSpread = spread t.cacheValid = false -proc setStrokeInRange*(t: FormattedText, a, b: int, color: Color, size: float32) = +proc setStrokeInRange*(fontCtx: FontContext, gl: GL, t: FormattedText, a, b: int, color: Color, size: float32) = + updateCacheIfNeeded(fontCtx, gl, t) for i in t.attrsInRange(a, b): t.mAttributes[i].strokeColor1 = color t.mAttributes[i].strokeSize = size t.mAttributes[i].isStrokeGradient = false t.cacheValid = false -proc setStrokeInRange*(t: FormattedText, a, b: int, color1, color2: Color, size: float32) = +proc setStrokeInRange*(fontCtx: FontContext, gl: GL, t: FormattedText, a, b: int, color1, color2: Color, size: float32) = + updateCacheIfNeeded(fontCtx, gl, t) for i in t.attrsInRange(a, b): t.mAttributes[i].strokeColor1 = color1 t.mAttributes[i].strokeColor2 = color2 @@ -357,7 +369,8 @@ proc attrIndexForRuneAtPos(t: FormattedText, pos: int): int = if pos == 0: return 0 # Shortcut result = t.mAttributes.lowerBoundIt(0, t.mAttributes.high, cmp(it.startRune, pos) <= 0) - 1 -proc uniInsert*(t: FormattedText, atIndex: int, s: string) = +proc uniInsert*(fontCtx: FontContext, gl: GL, t: FormattedText, atIndex: int, s: string) = + updateCacheIfNeeded(fontCtx, gl, t) t.cacheValid = false t.mText.uniInsert(s, atIndex) var ai = t.attrIndexForRuneAtPos(atIndex) @@ -376,7 +389,8 @@ proc getByteOffsetsForRunePositions(t: FormattedText, positions: openarray[int], res[i] = p r = positions[i] -proc uniDelete*(t: FormattedText, start, stop: int) = +proc uniDelete*(fontCtx: FontContext, gl: GL, t: FormattedText, start, stop: int) = + updateCacheIfNeeded(fontCtx, gl, t) t.cacheValid = false var sa = t.attrIndexForRuneAtPos(start) @@ -436,7 +450,8 @@ iterator attrsInLine(t: FormattedText, line: int): tuple[attrIndex, a, b: int] = if attributeBreaks: inc curAttrIndex -iterator runeWidthsInLine*(t: FormattedText, line: int): float32 = +iterator runeWidthsInLine*(fontCtx: FontContext, gl: GL, t: FormattedText, line: int): float32 = + updateCacheIfNeeded(fontCtx, gl, t) var first = true var charOff = 0 var r: Rune @@ -449,32 +464,32 @@ iterator runeWidthsInLine*(t: FormattedText, line: int): float32 = first = false while charOff <= attrEndIndex: fastRuneAt(t.mText, charOff, r, true) - let w = t.mAttributes[curAttrIndex].font.getAdvanceForRune(r) + let w = getAdvanceForRune(fontCtx, gl, t.mAttributes[curAttrIndex].font, r) yield w inc p -proc cursorOffsetForPositionInLine*(t: FormattedText, line, position: int): Coord = - t.updateCacheIfNeeded() +proc cursorOffsetForPositionInLine*(fontCtx: FontContext, gl: GL, t: FormattedText, line, position: int): Coord = + updateCacheIfNeeded(fontCtx, gl, t) if t.lines.len == 0: return var p = 0 - for width in t.runeWidthsInLine(line): + for width in runeWidthsInLine(fontCtx, gl, t, line): if p == position: break result += width inc p -proc xOfRuneAtPos*(t: FormattedText, position: int): Coord = - t.updateCacheIfNeeded() - let ln = min(t.lineOfRuneAtPos(position), t.lines.high) - result = t.cursorOffsetForPositionInLine(ln, position - t.lines[ln].startRune) +proc xOfRuneAtPos*(fontCtx: FontContext, gl: GL, t: FormattedText, position: int): Coord = + updateCacheIfNeeded(fontCtx, gl, t) + let ln = min(lineOfRuneAtPos(fontCtx, gl, t, position), t.lines.high) + result = cursorOffsetForPositionInLine(fontCtx, gl, t, ln, position - t.lines[ln].startRune) -proc getClosestCursorPositionToPointInLine*(t: FormattedText, line: int, p: Point, position: var int, offset: var Coord) = - t.updateCacheIfNeeded() +proc getClosestCursorPositionToPointInLine*(fontCtx: FontContext, gl: GL, t: FormattedText, line: int, p: Point, position: var int, offset: var Coord) = + updateCacheIfNeeded(fontCtx, gl, t) if line > t.lines.high: return var totalWidth = 0'f32 var pos = 0 - for width in t.runeWidthsInLine(line): + for width in runeWidthsInLine(fontCtx, gl, t, line): if p.x < totalWidth + width: if (totalWidth + width - p.x) > (p.x - totalWidth): position = pos + t.lines[line].startRune @@ -490,21 +505,21 @@ proc getClosestCursorPositionToPointInLine*(t: FormattedText, line: int, p: Poin if line < t.lines.high and position > 0: dec position offset = totalWidth -proc lineAtHeight*(t: FormattedText, height: Coord): int = - t.updateCacheIfNeeded() +proc lineAtHeight*(fontCtx: FontContext, gl: GL, t: FormattedText, height: Coord): int = + updateCacheIfNeeded(fontCtx, gl, t) result = lowerBoundIt(t.lines, t.lines.low, t.lines.high, cmp(it.top, height) <= 0) if result > 0: dec result -proc topOffset*(t: FormattedText): float32 = - t.updateCacheIfNeeded() +proc topOffset*(fontCtx: FontContext, gl: GL, t: FormattedText): float32 = + updateCacheIfNeeded(fontCtx, gl, t) case t.verticalAlignment of vaBottom: t.mBoundingSize.height - t.mTotalHeight of vaCenter: (t.mBoundingSize.height - t.mTotalHeight) / 2 else: 0 -proc getClosestCursorPositionToPoint*(t: FormattedText, p: Point, position: var int, offset: var Coord) = - let ln = t.lineAtHeight(p.y - t.topOffset) - t.getClosestCursorPositionToPointInLine(ln, p, position, offset) +proc getClosestCursorPositionToPoint*(fontCtx: FontContext, gl: GL, t: FormattedText, p: Point, position: var int, offset: var Coord) = + let ln = lineAtHeight(fontCtx, gl, t, p.y - topOffset(fontCtx, gl, t)) + getClosestCursorPositionToPointInLine(fontCtx, gl, t, ln, p, position, offset) proc runeLen*(t: FormattedText): int = # TODO: Optimize @@ -515,34 +530,39 @@ template len*(t: FormattedText): int = t.mText.len ################################################################################ # Some ugly api. Not recommended for use. May soon be removed. ################################################################################ -template attrOfRuneAtPos(t: FormattedText, pos: int): Attributes = +template attrOfRuneAtPos(fontCtx: FontContext, gl: GL, t: FormattedText, pos: int): Attributes = t.mAttributes[t.attrIndexForRuneAtPos(pos)] -proc colorOfRuneAtPos*(t: FormattedText, pos: int): tuple[color1, color2: Color, isGradient: bool] = +proc colorOfRuneAtPos*(fontCtx: FontContext, gl: GL, t: FormattedText, pos: int): tuple[color1, color2: Color, isGradient: bool] = + updateCacheIfNeeded(fontCtx, gl, t) let i = t.attrIndexForRuneAtPos(pos) result.color1 = t.mAttributes[i].textColor result.color2 = t.mAttributes[i].textColor2 result.isGradient = t.mAttributes[i].isTextGradient -proc shadowOfRuneAtPos*(t: FormattedText, pos: int): tuple[color: Color, offset: Size, radius, spread: float32] = +proc shadowOfRuneAtPos*(fontCtx: FontContext, gl: GL, t: FormattedText, pos: int): tuple[color: Color, offset: Size, radius, spread: float32] = + updateCacheIfNeeded(fontCtx, gl, t) let i = t.attrIndexForRuneAtPos(pos) result.color = t.mAttributes[i].shadowColor result.offset = t.mAttributes[i].shadowOffset result.radius = t.mAttributes[i].shadowRadius result.spread = t.mAttributes[i].shadowSpread -proc strokeOfRuneAtPos*(t: FormattedText, pos: int): tuple[color1, color2: Color, size: float32, isGradient: bool] = +proc strokeOfRuneAtPos*(fontCtx: FontContext, gl: GL, t: FormattedText, pos: int): tuple[color1, color2: Color, size: float32, isGradient: bool] = + updateCacheIfNeeded(fontCtx, gl, t) let i = t.attrIndexForRuneAtPos(pos) result.color1 = t.mAttributes[i].strokeColor1 result.color2 = t.mAttributes[i].strokeColor2 result.size = t.mAttributes[i].strokeSize result.isGradient = t.mAttributes[i].isStrokeGradient -proc fontOfRuneAtPos*(t: FormattedText, pos: int): Font = - t.attrOfRuneAtPos(pos).font +proc fontOfRuneAtPos*(fontCtx: FontContext, gl: GL, t: FormattedText, pos: int): Font = + updateCacheIfNeeded(fontCtx, gl, t) + attrOfRuneAtPos(fontCtx, gl, t, pos).font -proc trackingOfRuneAtPos*(t: FormattedText, pos: int): float32 = - t.attrOfRuneAtPos(pos).tracking +proc trackingOfRuneAtPos*(fontCtx: FontContext, gl: GL, t: FormattedText, pos: int): float32 = + updateCacheIfNeeded(fontCtx, gl, t) + attrOfRuneAtPos(fontCtx, gl, t, pos).tracking ################################################################################ # Drawing @@ -644,16 +664,18 @@ void compose() } """, false, "mediump") -type ForEachLineAttributeCallback = proc(c: GraphicsContext, t: FormattedText, p: var Point, curLine, endIndex: int, str: string) {.nimcall.} -proc forEachLineAttribute(c: GraphicsContext, inRect: Rect, origP: Point, t: FormattedText, cb: ForEachLineAttributeCallback) = +type ForEachLineAttributeCallback = proc(gfx: GraphicsContext, t: FormattedText, p: var Point, curLine, endIndex: int, str: string) {.nimcall.} +proc forEachLineAttribute(gfx: GraphicsContext, inRect: Rect, origP: Point, t: FormattedText, cb: ForEachLineAttributeCallback) = + template fontCtx: untyped = gfx.fontCtx + template gl: untyped = gfx.gl var p = origP let numLines = t.lines.len var curLine = 0 - let top = t.topOffset() + origP.y + let top = topOffset(fontCtx, gl, t) + origP.y let inRectValid = (inRect.y + inRect.height) > 0.01 while curLine < numLines: - p.x = origP.x + t.lineLeft(curLine) + p.x = origP.x + lineLeft(fontCtx, gl, t, curLine) p.y = t.lines[curLine].top + t.lines[curLine].baseline + top if inRectValid: if p.y < inRect.y: @@ -669,7 +691,7 @@ proc forEachLineAttribute(c: GraphicsContext, inRect: Rect, origP: Point, t: For for curAttrIndex, attrStartIndex, attrEndIndex in t.attrsInLine(curLine): if not lastAttrFont.isNil: - cb(c, t, p, curLine, lastCurAttrIndex, t.mText.substr(lastAttrStartIndex, lastAttrEndIndex)) + cb(gfx, t, p, curLine, lastCurAttrIndex, t.mText.substr(lastAttrStartIndex, lastAttrEndIndex)) lastCurAttrIndex = curAttrIndex lastAttrStartIndex = attrStartIndex @@ -687,7 +709,7 @@ proc forEachLineAttribute(c: GraphicsContext, inRect: Rect, origP: Point, t: For if t.mTruncationBehavior == tbEllipsis: symbols = "..." - runeWidth = lastAttrFont.getAdvanceForRune(Rune(symbols[0])) + runeWidth = getAdvanceForRune(fontCtx, gl, lastAttrFont, Rune(symbols[0])) let ellipsisWidth = runeWidth * symbols.len.float var width = ellipsisWidth @@ -696,7 +718,7 @@ proc forEachLineAttribute(c: GraphicsContext, inRect: Rect, origP: Point, t: For while index <= lastIndex: var r: Rune fastRuneAt(t.mText, index, r, true) - var w = lastAttrFont.getAdvanceForRune(r) + var w = getAdvanceForRune(fontCtx, gl, lastAttrFont, r) if w + width < t.mBoundingSize.width: width = width + w lastAttrEndIndex = index - 1 @@ -716,20 +738,21 @@ proc forEachLineAttribute(c: GraphicsContext, inRect: Rect, origP: Point, t: For p.x = t.mBoundingSize.width - width if not isCut: - cb(c, t, p, curLine, lastCurAttrIndex, t.mText.substr(lastAttrStartIndex, lastAttrEndIndex)) + cb(gfx, t, p, curLine, lastCurAttrIndex, t.mText.substr(lastAttrStartIndex, lastAttrEndIndex)) else: - cb(c, t, p, curLine, lastCurAttrIndex, t.mText.substr(lastAttrStartIndex, lastAttrEndIndex) & symbols) + cb(gfx, t, p, curLine, lastCurAttrIndex, t.mText.substr(lastAttrStartIndex, lastAttrEndIndex) & symbols) break else: - cb(c, t, p, curLine, lastCurAttrIndex, t.mText.substr(lastAttrStartIndex, lastAttrEndIndex)) + cb(gfx, t, p, curLine, lastCurAttrIndex, t.mText.substr(lastAttrStartIndex, lastAttrEndIndex)) curLine.inc -proc drawShadow(c: GraphicsContext, inRect: Rect, origP: Point, t: FormattedText) = +proc drawShadow(gfx: GraphicsContext, inRect: Rect, origP: Point, t: FormattedText) = # TODO: Optimize heavily - forEachLineAttribute(c, inRect, origP, t) do(c: GraphicsContext, t: FormattedText, p: var Point, curLine, curAttrIndex: int, str: string): - c.fillColor = t.mAttributes[curAttrIndex].shadowColor + forEachLineAttribute(gfx, inRect, origP, t) do(gfx: GraphicsContext, t: FormattedText, p: var Point, curLine, curAttrIndex: int, str: string): + template gl: untyped = gfx.gl + gfx.fillColor = t.mAttributes[curAttrIndex].shadowColor let font = t.mAttributes[curAttrIndex].font let oldBaseline = font.baseline font.baseline = bAlphabetic @@ -743,35 +766,34 @@ proc drawShadow(c: GraphicsContext, inRect: Rect, origP: Point, t: FormattedText if t.mAttributes[curAttrIndex].shadowRadius > 0.0 or t.mAttributes[curAttrIndex].shadowSpread > 0.0: var options = SOFT_SHADOW_ENABLED gradientAndStrokeComposition.options = options - let gl = c.gl - var cc = gl.getCompiledComposition(gradientAndStrokeComposition) + var cc = getCompiledComposition(gfx, gradientAndStrokeComposition) gl.useProgram(cc.program) - compositionDrawingDefinitions(cc, c, gl) + compositionDrawingDefinitions(cc, gfx, gl) const minShadowSpread = 0.17 # make shadow border smooth and great again setUniform("shadowRadius", t.mAttributes[curAttrIndex].shadowRadius / 8.0) setUniform("shadowSpread", t.mAttributes[curAttrIndex].shadowSpread + minShadowSpread) - setUniform("fillColor", c.fillColor) + setUniform("fillColor", gfx.fillColor) - gl.uniformMatrix4fv(uniformLocation("uModelViewProjectionMatrix"), false, c.transform) - setupPosteffectUniforms(cc) + gl.uniformMatrix4fv(uniformLocation("uModelViewProjectionMatrix"), false, gfx.transform) + setupPosteffectUniforms(gfx, cc) gl.activeTexture(GLenum(int(gl.TEXTURE0) + cc.iTexIndex)) gl.uniform1i(uniformLocation("texUnit"), cc.iTexIndex) - c.drawTextBase(font, pp, str) + gfx.drawTextBase(font, pp, str) else: - c.drawText(font, pp, str) + gfx.drawText(font, pp, str) font.baseline = oldBaseline p.x += pp.x - ppp.x -proc drawStroke(c: GraphicsContext, inRect: Rect, origP: Point, t: FormattedText) = +proc drawStroke(gfx: GraphicsContext, inRect: Rect, origP: Point, t: FormattedText) = # TODO: Optimize heavily - forEachLineAttribute(c, inRect, origP, t) do(c: GraphicsContext, t: FormattedText, p: var Point, curLine, curAttrIndex: int, str: string): + forEachLineAttribute(gfx, inRect, origP, t) do(gfx: GraphicsContext, t: FormattedText, p: var Point, curLine, curAttrIndex: int, str: string): const magicStrokeMaxSizeCoof = 0.46 let font = t.mAttributes[curAttrIndex].font @@ -781,12 +803,12 @@ proc drawStroke(c: GraphicsContext, inRect: Rect, origP: Point, t: FormattedText options = options or GRADIENT_ENABLED gradientAndStrokeComposition.options = options - let gl = c.gl - var cc = gl.getCompiledComposition(gradientAndStrokeComposition) + let gl = gfx.gl + var cc = getCompiledComposition(gfx, gradientAndStrokeComposition) gl.useProgram(cc.program) - compositionDrawingDefinitions(cc, c, gl) + compositionDrawingDefinitions(cc, gfx, gl) setUniform("strokeSize", min(t.mAttributes[curAttrIndex].strokeSize / 15, magicStrokeMaxSizeCoof)) @@ -798,59 +820,61 @@ proc drawStroke(c: GraphicsContext, inRect: Rect, origP: Point, t: FormattedText else: setUniform("fillColor", t.mAttributes[curAttrIndex].strokeColor1) - gl.uniformMatrix4fv(uniformLocation("uModelViewProjectionMatrix"), false, c.transform) - setupPosteffectUniforms(cc) + gl.uniformMatrix4fv(uniformLocation("uModelViewProjectionMatrix"), false, gfx.transform) + setupPosteffectUniforms(gfx, cc) gl.activeTexture(GLenum(int(gl.TEXTURE0) + cc.iTexIndex)) gl.uniform1i(uniformLocation("texUnit"), cc.iTexIndex) let oldBaseline = font.baseline font.baseline = bAlphabetic - c.drawTextBase(font, p, str) + gfx.drawTextBase(font, p, str) font.baseline = oldBaseline else: - c.fillColor = newColor(0, 0, 0, 0) + gfx.fillColor = newColor(0, 0, 0, 0) # Dirty hack to advance x position. Should be optimized, of course. - c.drawText(font, p, str) + gfx.drawText(font, p, str) -proc drawText*(c: GraphicsContext, origP: Point, t: FormattedText, inRect: Rect = zeroRect) = - t.updateCacheIfNeeded() +proc drawText*(gfx: GraphicsContext, origP: Point, t: FormattedText, inRect: Rect = zeroRect) = + template fontCtx: untyped = gfx.fontCtx + template gl: untyped = gfx.gl + updateCacheIfNeeded(fontCtx, gl, t) if t.overrideColor.a == 0: - if t.shadowAttrs.len > 0: c.drawShadow(inRect, origP, t) - if t.strokeAttrs.len > 0: c.drawStroke(inRect, origP, t) + if t.shadowAttrs.len > 0: gfx.drawShadow(inRect, origP, t) + if t.strokeAttrs.len > 0: gfx.drawStroke(inRect, origP, t) - forEachLineAttribute(c, inRect, origP, t) do(c: GraphicsContext, t: FormattedText, p: var Point, curLine, curAttrIndex: int, str: string): + forEachLineAttribute(gfx, inRect, origP, t) do(gfx: GraphicsContext, t: FormattedText, p: var Point, curLine, curAttrIndex: int, str: string): let font = t.mAttributes[curAttrIndex].font let oldBaseline = font.baseline font.baseline = bAlphabetic if t.mAttributes[curAttrIndex].isTextGradient: gradientAndStrokeComposition.options = GRADIENT_ENABLED - let gl = c.gl - var cc = gl.getCompiledComposition(gradientAndStrokeComposition) + let gl = gfx.gl + var cc = getCompiledComposition(gfx, gradientAndStrokeComposition) gl.useProgram(cc.program) - compositionDrawingDefinitions(cc, c, gl) + compositionDrawingDefinitions(cc, gfx, gl) setUniform("point_y", p.y - t.lines[curLine].baseline) setUniform("size_y", t.lines[curLine].height) setUniform("colorFrom", t.mAttributes[curAttrIndex].textColor) setUniform("colorTo", t.mAttributes[curAttrIndex].textColor2) - gl.uniformMatrix4fv(uniformLocation("uModelViewProjectionMatrix"), false, c.transform) - setupPosteffectUniforms(cc) + gl.uniformMatrix4fv(uniformLocation("uModelViewProjectionMatrix"), false, gfx.transform) + setupPosteffectUniforms(gfx, cc) gl.activeTexture(GLenum(int(gl.TEXTURE0) + cc.iTexIndex)) gl.uniform1i(uniformLocation("texUnit"), cc.iTexIndex) - c.drawTextBase(font, p, str) + gfx.drawTextBase(font, p, str) else: if t.overrideColor.a != 0: - c.fillColor = t.overrideColor + gfx.fillColor = t.overrideColor else: - c.fillColor = t.mAttributes[curAttrIndex].textColor - c.drawText(font, p, str) + gfx.fillColor = t.mAttributes[curAttrIndex].textColor + gfx.drawText(font, p, str) font.baseline = oldBaseline diff --git a/nimx/horizontal_list_view.nim b/nimx/horizontal_list_view.nim index 6ff351606..47389b521 100644 --- a/nimx/horizontal_list_view.nim +++ b/nimx/horizontal_list_view.nim @@ -7,7 +7,6 @@ import types import clip_view type - Adapter* = ref object of RootObj ListScrollListener = ref object of OnScrollListener @@ -26,7 +25,7 @@ type dirtyBoundsOrigin : Point itemClick: proc(pos : int) -proc newViewWrapper(view : View, pos: int): ViewWrapper = +proc newViewWrapper(view: View, pos: int): ViewWrapper = result.new result.v = view result.pos = pos @@ -37,13 +36,13 @@ method getView*(a: Adapter, position: int, convertView : View): View {.base.} = method setItemClickListener*(v: HorizontalListView, lis : proc(pos : int)) {.base.} = v.itemClick = lis -proc newHorListView*(r: Rect): HorizontalListView = +proc newHorListView*(gfx: GraphicsContext, r: Rect): HorizontalListView = result.new() result.items = @[] result.cleared = @[] - result.init(r) + result.init(gfx, r) -var offs = newPoint(0,0) +var offs = newPoint(0,0) # TODO: globals proc getMinPos(v : HorizontalListView):ViewWrapper = if v.items.len > 0: @@ -191,8 +190,8 @@ proc checkItemClick(v : HorizontalListView, p : Point) = if not v.itemClick.isNil: v.itemClick(wrap.pos) -method init*(v: HorizontalListView, r: Rect) = - procCall v.View.init(r) +method init*(v: HorizontalListView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.backgroundColor = newGrayColor(0.89) var sl : ListScrollListener new(sl) diff --git a/nimx/image.nim b/nimx/image.nim index b88a52d89..0c946d005 100644 --- a/nimx/image.nim +++ b/nimx/image.nim @@ -274,7 +274,7 @@ method serialize*(s: Serializer, v: Image) {.base.} = path = v.SelfContainedImage.mFilePath s.serialize("imagePath", path) -method deserialize*(s: Deserializer, v: var Image) {.base.} = +method deserialize*(s: Deserializer, v: var Image, gfx: RootRef) {.base.} = var imagePath: string s.deserialize("imagePath", imagePath) if imagePath.len > 0: @@ -458,7 +458,7 @@ when not web and not defined(ios): const asyncResourceLoad = not web and not defined(nimxAvoidSDL) and compileOption("threads") when asyncResourceLoad: - const loadAsyncTextureInMainThread = defined(android) or defined(ios) + const loadAsyncTextureInMainThread = true#defined(android) or defined(ios) import perform_on_main_thread, sdl2 import private/worker_queue diff --git a/nimx/image_preview.nim b/nimx/image_preview.nim index def8c4a12..030943892 100644 --- a/nimx/image_preview.nim +++ b/nimx/image_preview.nim @@ -25,7 +25,7 @@ type ImagePreview* = ref object of PanelView imageRect*: Rect #todo: fix this, make image setter -method init*(v: ImagePreview, r: Rect) = +method init*(v: ImagePreview, gfx: GraphicsContext, r: Rect) = let maxLen = max(v.image.size.width, v.image.size.height) var scale = 1.0 if maxLen > maxSize: @@ -41,57 +41,58 @@ method init*(v: ImagePreview, r: Rect) = viewRect.size.width = content.width + 2.0 viewRect.size.height = content.height + titleSize + 1.0 + 30.0 - procCall v.PanelView.init(viewRect) + procCall v.PanelView.init(gfx, viewRect) v.backgroundColor = newColor(0.2, 0.2, 0.2, 1.0) v.title = "Image Preview" v.imgScale = scale - let closeBttn = newButton(v, newPoint(viewRect.width - 16.0 - 1.0, 1.0), newSize(16, 16), "X") + let closeBttn = newButton(v, gfx, newPoint(viewRect.width - 16.0 - 1.0, 1.0), newSize(16, 16), "X") closeBttn.autoresizingMask = {afFlexibleMinX, afFlexibleMaxY} closeBttn.onAction do(): v.removeFromSuperview() -proc newImagePreview*(r: Rect, img: Image): ImagePreview = +proc newImagePreview*(gfx: GraphicsContext, r: Rect, img: Image): ImagePreview = result.new() result.image = img - result.init(r) + result.init(gfx, r) method draw*(v: ImagePreview, r: Rect) = procCall v.PanelView.draw(r) - let c = currentContext() - let f = systemFontOfSize(14.0) + template gfx: untyped = v.gfx + template fontCtx: untyped = gfx.fontCtx + let f = systemFontOfSize(fontCtx, 14.0) var titleRect: Rect titleRect.size.width = r.width titleRect.size.height = titleSize - c.fillColor = newColor(0.2, 0.2, 0.2) - c.drawRect(titleRect) + gfx.fillColor = newColor(0.2, 0.2, 0.2) + gfx.drawRect(titleRect) var contentRect: Rect contentRect.origin.x = 1.0 contentRect.origin.y = titleSize contentRect.size.width = r.width - 2.0 contentRect.size.height = r.height - titleSize - bottomSize - 1.0 - c.fillColor = newColor(0.5, 0.5, 0.5) - c.drawRect(contentRect) + gfx.fillColor = newColor(0.5, 0.5, 0.5) + gfx.drawRect(contentRect) - c.fillColor = newColor(0.9, 0.9, 0.9) - c.drawText(f, newPoint(5, 1), v.title) + gfx.fillColor = newColor(0.9, 0.9, 0.9) + gfx.drawText(f, newPoint(5, 1), v.title) # Draw Image v.imageRect.origin.x = 1.0 v.imageRect.origin.y = titleSize v.imageRect.size.width = v.image.size.width * v.imgScale v.imageRect.size.height = v.image.size.height * v.imgScale - c.drawImage(v.image, v.imageRect) + gfx.drawImage(v.image, v.imageRect) # Draw Info let sizeInfo = "Size: " & $v.image.size.width & " x " & $v.image.size.height - c.drawText(f, newPoint(5, r.height - bottomSize / 2.0), sizeInfo) + gfx.drawText(f, newPoint(5, r.height - bottomSize / 2.0), sizeInfo) var pathInfo = "Path: nil" if v.image.filePath.len != 0: pathInfo = "Path: " & $v.image.filePath - c.drawText(f, newPoint(5, r.height - bottomSize), pathInfo) + gfx.drawText(f, newPoint(5, r.height - bottomSize), pathInfo) # method onTouchEv*(v: ImagePreview, e: var Event) : bool = # discard procCall v.PanelView.onTouchEv(e) diff --git a/nimx/image_view.nim b/nimx/image_view.nim index 9d4599404..09781f44a 100644 --- a/nimx/image_view.nim +++ b/nimx/image_view.nim @@ -21,14 +21,14 @@ type imageMarginTop*: Coord imageMarginBottom*: Coord -proc newImageView*(r: Rect, image: Image = nil, fillRule = ImageFillRule.NoFill): ImageView = +proc newImageView*(gfx: GraphicsContext, r: Rect, image: Image = nil, fillRule = ImageFillRule.NoFill): ImageView = result.new result.image = image result.fillRule = fillRule - result.init(r) + result.init(gfx, r) -method init*(v: ImageView, r: Rect) = - procCall v.View.init(r) +method init*(v: ImageView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) proc image*(v: ImageView): Image = v.image proc `image=`*(v: ImageView, image: Image) = @@ -44,7 +44,7 @@ method clipType*(v: ImageView): ClipType = ctDefaultClip method draw(v: ImageView, r: Rect) = procCall v.View.draw(r) - let c = currentContext() + template c: untyped = v.gfx if v.image.isNil(): c.drawRect(r) diff --git a/nimx/inspector_panel.nim b/nimx/inspector_panel.nim index d815b3eb6..5c28f6bd4 100644 --- a/nimx/inspector_panel.nim +++ b/nimx/inspector_panel.nim @@ -19,23 +19,23 @@ proc moveSubviewToBack(v, s: View) = proc onPropertyChanged*(i: InspectorPanel, cb: proc(name: string)) = i.mOnPropertyChanged = cb -method init*(i: InspectorPanel, r: Rect) = +method init*(i: InspectorPanel, gfx: GraphicsContext, r: Rect) = i.draggable = false - procCall i.PanelView.init(r) + procCall i.PanelView.init(gfx, r) i.collapsible = true i.collapsed = true - let title = newLabel(newRect(22, 6, 96, 15)) + let title = newLabel(gfx, newRect(22, 6, 96, 15)) title.textColor = whiteColor() title.text = "Properties" i.addSubview(title) i.autoresizingMask = { afFlexibleMaxX } - i.inspectorView = InspectorView.new(newRect(0, i.titleHeight, r.width, r.height - i.titleHeight)) + i.inspectorView = InspectorView.new(gfx, newRect(0, i.titleHeight, r.width, r.height - i.titleHeight)) i.inspectorView.autoresizingMask = {afFlexibleWidth, afFlexibleMaxY} i.inspectorView.onPropertyChanged = proc(name: string) = if not i.mOnPropertyChanged.isNil: i.mOnPropertyChanged(name) - let sv = newScrollView(i.inspectorView) + let sv = newScrollView(gfx, i.inspectorView) sv.horizontalScrollBar = nil sv.autoresizingMask = {afFlexibleWidth, afFlexibleHeight} sv.setFrameOrigin(newPoint(sv.frame.x, i.titleHeight)) diff --git a/nimx/inspector_view.nim b/nimx/inspector_view.nim index a157f598e..25f1e7fe7 100644 --- a/nimx/inspector_view.nim +++ b/nimx/inspector_view.nim @@ -10,8 +10,8 @@ export linear_layout type InspectorView* = ref object of LinearLayout onPropertyChanged*: proc(name: string) -method init*(v: InspectorView, r: Rect) = - procCall v.LinearLayout.init(r) +method init*(v: InspectorView, gfx: GraphicsContext, r: Rect) = + procCall v.LinearLayout.init(gfx, r) v.horizontal = false proc setInspectedObject*[T](v: InspectorView, o: T) = @@ -31,6 +31,6 @@ proc setInspectedObject*[T](v: InspectorView, o: T) = visitor.requireGetter = true visitor.flags = { pfEditable } visitor.commit = proc() = - v.addSubview(propertyEditorForProperty(oo, visitor.name, visitor.setterAndGetter, onChange = onChanged(visitor))) + v.addSubview(propertyEditorForProperty(v.gfx, oo, visitor.name, visitor.setterAndGetter, onChange = onChanged(visitor))) o.visitProperties(visitor) diff --git a/nimx/keyboard.nim b/nimx/keyboard.nim index ee0def3f1..862f4b003 100644 --- a/nimx/keyboard.nim +++ b/nimx/keyboard.nim @@ -3,7 +3,9 @@ ## Scan Code defines specific physical key on keyboard, and enumeration ## member names do not define characters but rather 'common' characters ## on those keys places (so that e.g. 'k' and 'K' share same scan code). -when defined(js) or defined(emscripten): +import backends + +when backend.input == InputApi.web: import private/js_platform_detector type VirtualKey* {.pure.} = enum @@ -225,7 +227,7 @@ proc anyOsModifier*(s: ModifiersSet): bool = when defined(macosx): s.anyGui() - elif defined(js) or defined(emscripten): + elif web: if isMacOs: s.anyGui() else: diff --git a/nimx/layout.nim b/nimx/layout.nim index 1c70f7fe8..b69fa965d 100644 --- a/nimx/layout.nim +++ b/nimx/layout.nim @@ -165,7 +165,9 @@ proc layoutAux(rootView: NimNode, body: NimNode): NimNode = for i in 1 ..< numViews: if initializers[i].identOrSym: # Explicit type. Need to cal init() - result.add(newCall("init", ids[i], newIdentNode("zeroRect"))) + template x: untyped = ids[i] + result.add quote do: + init(`x`, `rootView`.gfx, `zeroRect`) for i in 1 ..< numViews: result.add(newCall("addSubview", ids[childParentRelations[i]], ids[i])) diff --git a/nimx/linear_layout.nim b/nimx/linear_layout.nim index d815e7acc..a0b8ba4b8 100644 --- a/nimx/linear_layout.nim +++ b/nimx/linear_layout.nim @@ -15,17 +15,17 @@ type hoveredDivider: int initialDragPos: Point -proc newHorizontalLayout*(r: Rect): LinearLayout = - result = LinearLayout.new(r) +proc newHorizontalLayout*(gfx: GraphicsContext, r: Rect): LinearLayout = + result = LinearLayout.new(gfx, r) result.name = "HorizontalLayout" -proc newVerticalLayout*(r: Rect): LinearLayout = - result = LinearLayout.new(r) +proc newVerticalLayout*(gfx: GraphicsContext, r: Rect): LinearLayout = + result = LinearLayout.new(gfx, r) result.name = "VerticalLayout" result.mHorizontal = false -method init*(v: LinearLayout, r: Rect) = - procCall v.View.init(r) +method init*(v: LinearLayout, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.mPadding = 1 v.mHorizontal = true v.hoveredDivider = -1 diff --git a/nimx/menu.nim b/nimx/menu.nim index 3c0303d84..20fcf3069 100644 --- a/nimx/menu.nim +++ b/nimx/menu.nim @@ -70,55 +70,57 @@ type const menuItemHeight = 20.Coord -proc minMenuWidth(item: MenuItem): Coord = - let font = systemFont() +proc minMenuWidth(gfx: GraphicsContext, item: MenuItem): Coord = + template fontCtx: untyped = gfx.fontCtx + template gl: untyped = gfx.gl + let font = systemFont(fontCtx) for c in item.children: - let sz = font.sizeOfString(c.title).width + let sz = sizeOfString(fontCtx, gl, font, c.title).width if sz > result: result = sz -proc newViewWithMenuItems(item: MenuItem, size: Size): MenuView = - let width = max(size.width, minMenuWidth(item) + 20) - result = MenuView.new(newRect(0, 0, width, item.children.len.Coord * menuItemHeight)) +proc newViewWithMenuItems(gfx: GraphicsContext, item: MenuItem, size: Size): MenuView = + let width = max(size.width, minMenuWidth(gfx, item) + 20) + result = MenuView.new(gfx, newRect(0, 0, width, item.children.len.Coord * menuItemHeight)) result.item = item result.highlightedRow = -1 var yOff = 0.Coord for i, item in item.children: var cell: TableViewCell if item.title == "-": - let sep = SeparatorView.new(newRect(0, 0, width, size.height)) - cell = newTableViewCell(sep) + let sep = SeparatorView.new(gfx, newRect(0, 0, width, size.height)) + cell = newTableViewCell(gfx, sep) else: - let label = newLabel(newRect(0, 0, width, size.height)) + let label = newLabel(gfx, newRect(0, 0, width, size.height)) label.text = item.title - cell = newTableViewCell(label) + cell = newTableViewCell(gfx, label) cell.setFrameOrigin(newPoint(0, yOff)) cell.row = i cell.selected = false if item.children.len > 0: - let triangleView = TriangleView.new(newRect(width - 20, 0, 20, menuItemHeight)) + let triangleView = TriangleView.new(gfx, newRect(width - 20, 0, 20, menuItemHeight)) cell.addSubview(triangleView) result.addSubview(cell) yOff += menuItemHeight method draw(v: MenuView, r: Rect) = - let c = currentContext() + template c: untyped = v.gfx c.fillColor = newGrayColor(0.7) c.strokeWidth = 0 c.drawRoundedRect(v.bounds, 5) method draw(v: TriangleView, r: Rect) = let cell = v.enclosingTableViewCell() - let c = currentContext() + template c: untyped = v.gfx c.fillColor = blackColor() if not cell.isNil and cell.selected: c.fillColor = whiteColor() c.drawTriangle(v.bounds, 0) method draw(v: SeparatorView, r: Rect) = - let c = currentContext() + template c: untyped = v.gfx c.fillColor = newGrayColor(0.2) c.strokeWidth = 0 var r = v.bounds @@ -135,7 +137,7 @@ proc removeMenuView(v: MenuView) = v = v.submenu proc popupAtPoint*(m: MenuItem, v: View, p: Point, size: Size = newSize(150.0, menuItemHeight)) = - let mv = newViewWithMenuItems(m, size) + let mv = newViewWithMenuItems(v.gfx, m, size) var wp = v.convertPointToWindow(p) let win = v.window @@ -192,7 +194,7 @@ proc popupAtPoint*(m: MenuItem, v: View, p: Point, size: Size = newSize(150.0, m if selectedItem.children.len > 0: # Create submenu view - let sub = newViewWithMenuItems(selectedItem, size) + let sub = newViewWithMenuItems(v.gfx, selectedItem, size) var pt = newPoint(selectedCell.bounds.width, selectedCell.bounds.y) pt = selectedCell.convertPointToWindow(pt) sub.setFrameOrigin(pt) diff --git a/nimx/meta_extensions/property_desc.nim b/nimx/meta_extensions/property_desc.nim index 9cc9ff3c5..1d9da96d2 100644 --- a/nimx/meta_extensions/property_desc.nim +++ b/nimx/meta_extensions/property_desc.nim @@ -1,4 +1,4 @@ -import macros, tables, typetraits +import macros, tables import nimx/class_registry export class_registry diff --git a/nimx/meta_extensions/serializers_gen.nim b/nimx/meta_extensions/serializers_gen.nim index 2837f9e8c..91d3c2ff2 100644 --- a/nimx/meta_extensions/serializers_gen.nim +++ b/nimx/meta_extensions/serializers_gen.nim @@ -3,23 +3,25 @@ import nimx / ui_resource export ui_resource import macros -proc genSerializeCall(view, serializer, field: NimNode, isSerialize: bool): NimNode {.compileTime.}= - let call = if isSerialize: ident("serialize") else: ident("deserialize") +proc genSerializeCall(view, serializer, field: NimNode): NimNode {.compileTime.}= + let call = ident("serialize") let fieldLit = newLit($field) - # if isSerialize: result = quote do: `serializer`.`call`(`fieldLit`, `view`.`field`) - # else: - # result = quote do: - # `serializer`.`call`(`view`.`field`) - # echo "genSerializeCall ", repr(result) +proc genDeserializeCall(view, deserializer, field: NimNode): NimNode {.compileTime.}= + let call = ident("deserialize") + let gfx = ident("gfx") + let fieldLit = newLit($field) + result = quote do: + `deserializer`.`call`(`fieldLit`, `view`.`field`, `gfx`) macro genSerializers(typdesc: typed{nkSym}): untyped= result = nnkStmtList.newNimNode() let viewArg = ident("v") let serArg = ident("s") + let gfx = ident("gfx") var serializerBody = nnkStmtList.newNimNode() var deserializerBody = nnkStmtList.newNimNode() @@ -33,11 +35,11 @@ macro genSerializers(typdesc: typed{nkSym}): untyped= procCall `viewArg`.`parent`.serializeFields(`serArg`) deserializerBody.add quote do: - procCall `viewArg`.`parent`.deserializeFields(`serArg`) + procCall `viewArg`.`parent`.deserializeFields(`serArg`, `gfx`) for p in typdesc.propertyDescs(): - let serCall = genSerializeCall(viewArg, serArg, ident(p.name), true) - let desCall = genSerializeCall(viewArg, serArg, ident(p.name), false) + let serCall = genSerializeCall(viewArg, serArg, ident(p.name)) + let desCall = genDeserializeCall(viewArg, serArg, ident(p.name)) serializerBody.add quote do: `serCall` deserializerBody.add quote do: @@ -47,7 +49,7 @@ macro genSerializers(typdesc: typed{nkSym}): untyped= method serializeFields*(`viewArg`: `typdesc`, `serArg`: Serializer) = `serializerBody` - method deserializeFields*(`viewArg`: `typdesc`, `serArg`: Deserializer)= + method deserializeFields*(`viewArg`: `typdesc`, `serArg`: Deserializer, `gfx`: RootRef)= `deserializerBody` template genSerializeCodeForView*(c: typed)= diff --git a/nimx/naketools.nim b/nimx/naketools.nim index 942df289d..9f6d85227 100644 --- a/nimx/naketools.nim +++ b/nimx/naketools.nim @@ -613,7 +613,8 @@ proc makeEmscriptenPreloadData(b: Builder): string = result = b.nimcachePath / "preload.js" let packagerPy = emcc.parentDir() / "tools" / "file_packager.py" createDir(b.nimcachePath) - var args = @["python", packagerPy.quoteShell(), b.buildRoot / "main.data", "--js-output=" & result] + let python = getEnv("PYTHON", "python") + var args = @[python, packagerPy.quoteShell(), b.buildRoot / "main.data", "--js-output=" & result] for p in b.emscriptenPreloadFiles: args.add(["--preload", p]) direShell(args) @@ -813,9 +814,9 @@ proc build*(b: Builder) = if b.avoidSDL: b.nimFlags.add("-d:nimxAvoidSDL") if b.debugMode: - b.nimFlags.add(["-d:debug"]) - if b.platform != "js": - b.nimFlags.add(["--stackTrace:on", "--lineTrace:on"]) + b.nimFlags.add(["--debugger:on"]) + if b.platform == "js": + b.nimFlags.add(["--stackTrace:off", "--lineTrace:off"]) else: b.nimFlags.add(["-d:release", "--opt:speed", "-d:noAutoGLerrorCheck"]) diff --git a/nimx/numeric_text_field.nim b/nimx/numeric_text_field.nim index e7b54e674..b4f5b5120 100644 --- a/nimx/numeric_text_field.nim +++ b/nimx/numeric_text_field.nim @@ -11,13 +11,13 @@ type NumericTextField* = ref object of TextField touchAnim: Animation directionLeft: bool -proc newNumericTextField*(r: Rect, precision: uint = 2): NumericTextField = +proc newNumericTextField*(gfx: GraphicsContext, r: Rect, precision: uint = 2): NumericTextField = result.new() - result.init(r) + result.init(gfx, r) result.precision = precision -method init*(v: NumericTextField, r: Rect) = - procCall v.TextField.init(r) +method init*(v: NumericTextField, gfx: GraphicsContext, r: Rect) = + procCall v.TextField.init(gfx, r) v.precision = 2 v.formattedText.horizontalAlignment = haCenter @@ -54,9 +54,10 @@ void compose() { proc drawArrows(v: NumericTextField) = const arrowMargin = 10 - arrowComposition.draw newRect(0, 0, arrowMargin, v.bounds.height): + template c: untyped = v.gfx + draw c, arrowComposition, newRect(0, 0, arrowMargin, v.bounds.height): setUniform("uAngle", Coord(PI)) - arrowComposition.draw newRect(v.bounds.width - arrowMargin, 0, arrowMargin, v.bounds.height): + draw c, arrowComposition, newRect(v.bounds.width - arrowMargin, 0, arrowMargin, v.bounds.height): setUniform("uAngle", Coord(0)) method draw*(t: NumericTextField, r: Rect) = diff --git a/nimx/outline_view.nim b/nimx/outline_view.nim index d4c55bb5b..e9b19cb52 100644 --- a/nimx/outline_view.nim +++ b/nimx/outline_view.nim @@ -42,8 +42,8 @@ type IndexPath* = seq[int] -method init*(v: OutlineView, r: Rect) = - procCall v.View.init(r) +method init*(v: OutlineView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.rootItem = ItemNode.new() v.rootItem.expandable = true v.rootItem.expanded = true @@ -52,8 +52,8 @@ method init*(v: OutlineView, r: Rect) = const rowHeight = 20.Coord -proc drawDisclosureTriangle(disclosed: bool, r: Rect) = - currentContext().drawTriangle(r, if disclosed: Coord(PI / 2.0) else: Coord(0)) +proc drawDisclosureTriangle(ctx: GraphicsContext, disclosed: bool, r: Rect) = + ctx.drawTriangle(r, if disclosed: Coord(PI / 2.0) else: Coord(0)) template xOffsetForIndexPath(ip: IndexPath): Coord = Coord(offsetOutline + ip.len * offsetOutline * 2) @@ -68,11 +68,11 @@ proc configureCellAUX(v: OutlineView, n: ItemNode, y: Coord, indexPath: IndexPat proc drawNode(v: OutlineView, n: ItemNode, y: var Coord, indexPath: var IndexPath) = if n.filtered: return - let c = currentContext() + template c: untyped = v.gfx v.configureCellAUX(n, y, indexPath) n.cell.drawWithinSuperview() if n.expandable and n.children.len > 0: - drawDisclosureTriangle(n.expanded, newRect(n.cell.frame.x - 6 - offsetOutline * 2 - rowHeight * 0.5 , y - rowHeight * 0.5, rowHeight * 2.0, rowHeight * 2.0)) + drawDisclosureTriangle(c, n.expanded, newRect(n.cell.frame.x - 6 - offsetOutline * 2 - rowHeight * 0.5 , y - rowHeight * 0.5, rowHeight * 2.0, rowHeight * 2.0)) y += rowHeight diff --git a/nimx/panel_view.nim b/nimx/panel_view.nim index a815cac60..2b0ee65ee 100644 --- a/nimx/panel_view.nim +++ b/nimx/panel_view.nim @@ -27,8 +27,8 @@ template collapsed*(v: PanelView): bool = v.mCollapsed # PanelView implementation -method init*(v: PanelView, r: Rect) = - procCall v.View.init(r) +method init*(v: PanelView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.backgroundColor = newColor(0.5, 0.5, 0.5, 0.5) v.mCollapsed = false v.collapsible = false @@ -59,7 +59,7 @@ void compose() { method draw(v: PanelView, r: Rect) = # Draws Panel View - let c = currentContext() + template c: untyped = v.gfx # Top label c.fillColor = newGrayColor(0.05, 0.8) diff --git a/nimx/plot.nim b/nimx/plot.nim new file mode 100644 index 000000000..298117a66 --- /dev/null +++ b/nimx/plot.nim @@ -0,0 +1,208 @@ +import std/strutils +import nimx/[ + context, + control, + event, + font + ] + +type ModelXY*[T] = seq[tuple[x: T, y: T]] + ## y=f(x) discrete data model + +type ModelXYColor*[T] = seq[tuple[x: T, y: T, color: Color]] + ## t=f(x) discrete data model with colored dots + +type PlotXY* = ref object of Control + ## Plotting widgets that implements rendering of "y=f(x)" function. + title*: string + labelX*: string + labelY*: string + boundary*: float32 + dotSize*: int + gridstep*: float32 + drawMedian*: bool + model*: ModelXYColor[float64] + highlightedPoint: int + modelBounds: tuple[minx: float64, maxx: float64, miny: float64, maxy: float64] + scale: tuple[x: float64, y: float64] + poly: seq[Coord] + +proc modelBounds*(mxy: PlotXY): tuple[minx: float64, maxx: float64, miny: float64, maxy: float64] = mxy.modelBounds + +proc updateModel(mxy: PlotXY) = + mxy.modelBounds.minx = 100000 + mxy.modelBounds.maxx = -100000 + mxy.modelBounds.miny = 100000 + mxy.modelBounds.maxy = -100000 + + mxy.scale.x = 0 + mxy.scale.y = 0 + + mxy.poly = @[] + + for point in mxy.model.items(): + mxy.modelBounds.minx = min(point.x, mxy.modelBounds.minx) + mxy.modelBounds.miny = min(point.y, mxy.modelBounds.miny) + mxy.modelBounds.maxx = max(point.x, mxy.modelBounds.maxx) + mxy.modelBounds.maxy = max(point.y, mxy.modelBounds.maxy) + + mxy.scale.x = (mxy.bounds.width - mxy.boundary * 2) / (mxy.modelBounds.maxx - mxy.modelBounds.minx) + mxy.scale.y = (mxy.bounds.height - mxy.boundary * 2) / (mxy.modelBounds.maxy - mxy.modelBounds.miny) + + for point in mxy.model.items(): + mxy.poly.add( mxy.boundary + (Coord(point.x.float32) - mxy.modelBounds.minx) * mxy.scale.x) + mxy.poly.add(-(mxy.boundary + (Coord(point.y.float32) - mxy.modelBounds.miny) * mxy.scale.y) + Coord(mxy.bounds.height)) + +method init(mxy: PlotXY, gfx: GraphicsContext, r: Rect) = + procCall mxy.Control.init(gfx, r) + mxy.backgroundColor = whiteColor() + mxy.boundary = 50.0 + mxy.gridstep = 15.0 + mxy.dotSize = 4 + mxy.highlightedPoint = -1 + mxy.drawMedian = false + +proc newPlotXY*(gfx: GraphicsContext, r: Rect, model: ModelXYColor[float64]): PlotXY = + result.new() + result.model = model + result.init(gfx, r) + +method draw*(mxy: PlotXY, r: Rect) = + procCall mxy.View.draw(r) + + updateModel(mxy) + + template gfx: untyped = mxy.gfx + + ## Draw grid + gfx.strokeColor = newGrayColor(0.7) + gfx.strokeWidth = 1 + + for i in 0..mxy.gridstep.int: + let + pStart = newPoint(mxy.boundary, r.size.height - mxy.boundary - i.float32 * (r.size.height - mxy.boundary * 2) / mxy.gridstep) + pEnd = newPoint(r.size.width - mxy.boundary, r.size.height - mxy.boundary - i.float32 * (r.size.height - mxy.boundary * 2) / mxy.gridstep) + gfx.drawLine(pStart, pEnd) + + for i in 0..mxy.gridstep.int: + let + pStart = newPoint(mxy.boundary + i.float32 * (r.size.width - mxy.boundary * 2) / mxy.gridstep, mxy.boundary) + pEnd = newPoint(mxy.boundary + i.float32 * (r.size.width - mxy.boundary * 2) / mxy.gridstep, r.size.height - mxy.boundary) + gfx.drawLine(pStart, pEnd) + + ## Draw graph + gfx.fillColor = blackColor() + gfx.strokeColor = blackColor() + gfx.strokeWidth = 2 + + if mxy.model.len() > 0: + if mxy.drawMedian: + gfx.strokeColor = newColor(0.0, 1.0, 0.0) + gfx.drawLine(newPoint(mxy.poly[0], mxy.poly[1]), newPoint(mxy.poly[mxy.poly.len() - 2], mxy.poly[mxy.poly.len() - 1])) + + gfx.strokeColor = blackColor() + for i in countup(0, mxy.poly.len()-3, 2): + gfx.drawLine( + newPoint(mxy.poly[i], mxy.poly[i+1]), + newPoint(mxy.poly[i+2], mxy.poly[i+3]) + ) + for i in countup(0, mxy.poly.len()-3, 2): + gfx.strokeColor = mxy.model[(i/2).int].color + gfx.fillColor = gfx.strokeColor + + if mxy.highlightedPoint != -1: + if i == mxy.highlightedPoint or i == mxy.highlightedPoint + 1: + gfx.drawEllipseInRect(newRect(mxy.poly[i] - 6, mxy.poly[i+1] - 6, 12, 12)) + gfx.drawEllipseInRect(newRect(mxy.poly[i] - mxy.dotSize.Coord, mxy.poly[i+1] - mxy.dotSize.Coord, mxy.dotSize.Coord * 2, mxy.dotSize.Coord * 2)) + gfx.drawEllipseInRect(newRect(mxy.poly[^2] - mxy.dotSize.Coord, mxy.poly[^1] - mxy.dotSize.Coord, mxy.dotSize.Coord * 2, mxy.dotSize.Coord * 2)) + + gfx.fillColor = blackColor() + gfx.strokeColor = blackColor() + let font = systemFont(gfx.fontCtx) + + ## Draw title + var pt = centerInRect(font.sizeOfString(mxy.title), newRect(0.0, 0.0, r.size.width, mxy.boundary)) + gfx.drawText(font, pt, mxy.title) + + for i in 0..mxy.gridstep.int: + let pt = newPoint(2, r.size.height - mxy.boundary - i.float32 * (r.size.height - mxy.boundary * 2) / mxy.gridstep) + let stepValue = (mxy.modelBounds.maxy - mxy.modelBounds.miny) / mxy.gridstep * i.float32 + mxy.modelBounds.miny + gfx.drawText(font, pt, $stepValue.int) + + for i in 0..mxy.gridstep.int: + pt = newPoint(mxy.boundary + i.float32 * (r.size.width - mxy.boundary * 2) / mxy.gridstep, r.size.height - mxy.boundary) + let stepValue = (mxy.modelBounds.maxx - mxy.modelBounds.minx) / mxy.gridstep * i.float32 + mxy.modelBounds.minx + gfx.drawText(font, pt, $stepValue.int) + + ## Draw axes labels + pt = newPoint(mxy.boundary / 2, mxy.boundary / 2) + gfx.drawText(font, pt, mxy.labelY) + + if mxy.highlightedPoint > -1: + let index: int = (mxy.highlightedPoint.float).int + let x = mxy.model[(index / 2).int].x + let y = mxy.model[(index / 2).int].y + gfx.drawText(font, newPoint(mxy.poly[index], mxy.poly[index+1] - 20.0), "($#, $#)" % [$x, $y]) + + pt = newPoint(r.size.width - mxy.boundary * 2, r.size.height - mxy.boundary / 1.5) + gfx.drawText(font, pt, mxy.labelX) + +method onMouseDown(mxy: PlotXY, e: var Event): bool {.base.} = + let pos = e.localPosition + if pos.x < mxy.boundary or pos.x > mxy.bounds.width - mxy.boundary: + return true + if pos.y < mxy.boundary or pos.y > mxy.bounds.height - mxy.boundary: + return true + + let xpart = ((pos.x - mxy.boundary) / (mxy.bounds.width - 2 * mxy.boundary)) + let ypart = (pos.y / (mxy.bounds.height - 2 * mxy.boundary)) + + var hp: Point = newPoint(0.0, 0.0) + hp.x = xpart * (mxy.modelBounds.maxx - mxy.modelBounds.minx) + hp.y = ypart * (mxy.modelBounds.maxy - mxy.modelBounds.miny) + + for i, v in mxy.model.pairs(): + if v.x > hp.x: + if hp.x - mxy.model[i-1].x < mxy.model[i].x - hp.x: + mxy.highlightedPoint = (i - 1) * 2 + else: + mxy.highlightedPoint = i * 2 + break + + mxy.setNeedsDisplay() + return true + +method onMouseUp(mxy: PlotXY, e: var Event): bool {.base.} = + mxy.highlightedPoint = -1 + mxy.setNeedsDisplay() + return true + +method onTouchEv*(t: PlotXY, e: var Event): bool = + case e.buttonState + of bsDown: onMouseDown(t, e) + of bsUp: onMouseUp(t, e) + of bsUnknown: false + + +# nim r --threads:on nimxplot +when isMainModule: + import nimx/[window, layout] + # import nimxplot + + runApplication: + newFullscreenWindow().makeLayout: + - PlotXY: + leading == super + 10 + trailing == super - 10 + top == super + 10 + bottom == super - 10 + title: "Dependency of Y from X" + labelY: "Y" + labelX: "X" + model: @[ + (x: 0.0, y: 0.0, color: newColor(0, 0, 0)), + (x: 10.0, y: 10.0, color: newColor(1, 0, 0)), + (x: 20.0, y: 10.0, color: newColor(0, 1, 0)), + (x: 60.0, y: 300.0, color: newColor(0, 0, 1)), + (x: 100.0, y: 200.0, color: newColor(1, 1, 0)) + ] diff --git a/nimx/popup_button.nim b/nimx/popup_button.nim index 3b2a5e709..4b14cefcb 100644 --- a/nimx/popup_button.nim +++ b/nimx/popup_button.nim @@ -10,12 +10,12 @@ type PopupButton* = ref object of Control mItems: seq[MenuItem] mSelectedIndex: int -proc newPopupButton(r: Rect): PopupButton = +proc newPopupButton(gfx: GraphicsContext, r: Rect): PopupButton = result.new() - result.init(r) + result.init(gfx, r) -method init*(b: PopupButton, r: Rect) = - procCall b.Control.init(r) +method init*(b: PopupButton, gfx: GraphicsContext, r: Rect) = + procCall b.Control.init(gfx, r) b.mSelectedIndex = -1 proc `items=`*(b: PopupButton, items: openarray[string]) = @@ -35,8 +35,8 @@ proc `items=`*(b: PopupButton, items: openarray[string]) = b.sendAction(Event(kind: etUnknown)) b.setNeedsDisplay() -proc newPopupButton*(parent: View = nil, position: Point = newPoint(0, 0), size: Size = newSize(100, 20), items: openarray[string]=[], selectedIndex: int=0): PopupButton = - result = newPopupButton(newRect(position.x, position.y, size.width, size.height)) +proc newPopupButton*(parent: View = nil, gfx: GraphicsContext, position: Point = newPoint(0, 0), size: Size = newSize(100, 20), items: openarray[string]=[], selectedIndex: int=0): PopupButton = + result = newPopupButton(gfx, newRect(position.x, position.y, size.width, size.height)) result.mSelectedIndex = selectedIndex result.items = items if not isNil(parent): @@ -83,14 +83,15 @@ void compose() { """ method draw(b: PopupButton, r: Rect) = - pbComposition.draw b.bounds: + template gfx: untyped = b.gfx + template fontCtx: untyped = gfx.fontCtx + draw gfx, pbComposition, b.bounds: setUniform("uFillColorStart", newColor(0.31, 0.60, 0.98)) setUniform("uFillColorEnd", newColor(0.09, 0.42, 0.88)) if b.mSelectedIndex >= 0 and b.mSelectedIndex < b.mItems.len: - let c = currentContext() - c.fillColor = blackColor() - let font = systemFont() - c.drawText(font, newPoint(4, b.bounds.y + (b.bounds.height - font.height) / 2), b.mItems[b.mSelectedIndex].title) + gfx.fillColor = blackColor() + let font = systemFont(fontCtx) + gfx.drawText(font, newPoint(4, b.bounds.y + (b.bounds.height - font.height) / 2), b.mItems[b.mSelectedIndex].title) method onTouchEv(b: PopupButton, e: var Event): bool = if b.mItems.len > 0: diff --git a/nimx/portable_gl.nim b/nimx/portable_gl.nim index b813eb695..2ad0d94e2 100644 --- a/nimx/portable_gl.nim +++ b/nimx/portable_gl.nim @@ -391,9 +391,6 @@ else: template isEmpty*(obj: TextureRef or FramebufferRef or RenderbufferRef): bool = obj == 0 -# TODO: This is a quick and dirty hack for render to texture. -var globalGL: GL - proc newGL*(canvas: ref RootObj): GL = when defined js: asm """ @@ -421,9 +418,6 @@ proc newGL*(canvas: ref RootObj): GL = """ else: result.new() - globalGL = result - -proc sharedGL*(): GL = globalGL proc shaderInfoLog*(gl: GL, s: ShaderRef): string = when defined js: diff --git a/nimx/private/text_drawing.nim b/nimx/private/text_drawing.nim index e51b1dd0c..0da8e5591 100644 --- a/nimx/private/text_drawing.nim +++ b/nimx/private/text_drawing.nim @@ -1,10 +1,6 @@ import unicode import nimx/font, nimx/composition, nimx/context, nimx/types -var textSubpixelDrawing = true -proc enableTextSubpixelDrawing*(state: bool) = - textSubpixelDrawing = state - let fontComposition = newComposition(""" attribute vec4 aPosition; @@ -217,7 +213,7 @@ proc drawTextBase*(c: GraphicsContext, font: Font, pt: var Point, text: string) n = 0 let off = n * 16 - font.getQuadDataForRune(ch, c.vertexes, off, newTexture, pt) + getQuadDataForRune(c.fontCtx, gl, font, ch, c.vertexes, off, newTexture, pt) if texture != newTexture: if n > 0: flush() @@ -235,9 +231,9 @@ proc drawText*(c: GraphicsContext, font: Font, pt: var Point, text: string) = # assume orthographic projection with units = screen pixels, origin at top left let gl = c.gl var cc : CompiledComposition - var subpixelDraw = textSubpixelDrawing + var subpixelDraw = c.fontCtx.enableSubpixelDrawing - if hasPostEffect(): + if hasPostEffect(c): subpixelDraw = false when defined(android) or defined(ios): @@ -250,11 +246,11 @@ proc drawText*(c: GraphicsContext, font: Font, pt: var Point, text: string) = subpixelDraw = false if subpixelDraw: - cc = gl.getCompiledComposition(fontSubpixelCompositionWithDynamicBase) + cc = getCompiledComposition(c, fontSubpixelCompositionWithDynamicBase) gl.blendColor(c.fillColor.r, c.fillColor.g, c.fillColor.b, c.fillColor.a) gl.blendFunc(gl.CONSTANT_COLOR, gl.ONE_MINUS_SRC_COLOR) else: - cc = gl.getCompiledComposition(fontComposition) + cc = getCompiledComposition(c, fontComposition) gl.useProgram(cc.program) @@ -263,7 +259,7 @@ proc drawText*(c: GraphicsContext, font: Font, pt: var Point, text: string) = setUniform("preScale", preScale) gl.uniformMatrix4fv(uniformLocation("uModelViewProjectionMatrix"), false, c.transform) - setupPosteffectUniforms(cc) + setupPosteffectUniforms(c, cc) gl.activeTexture(GLenum(int(gl.TEXTURE0) + cc.iTexIndex)) gl.uniform1i(uniformLocation("texUnit"), cc.iTexIndex) diff --git a/nimx/private/windows/appkit_window.nim b/nimx/private/windows/appkit_window.nim index c05c1c8dc..41cf93d6d 100644 --- a/nimx/private/windows/appkit_window.nim +++ b/nimx/private/windows/appkit_window.nim @@ -158,7 +158,6 @@ static void CreateApplicationMenus(void) { type AppkitWindow* = ref object of Window nativeWindow: pointer # __NimxWindow__ mNativeView: pointer # __NimxView__ - renderingContext: GraphicsContext inLiveResize: bool type AppDelegate = ref object @@ -208,7 +207,7 @@ proc initCommon(w: AppkitWindow, r: view.Rect) = w.mNativeView = nativeView # The context has to be inited before makeKeyAndOrderFront. - w.renderingContext = newGraphicsContext() + w.gfx = newGraphicsContext() {.emit: """ [win makeKeyAndOrderFront:nil]; """.} @@ -220,7 +219,7 @@ template nativeView(w: AppkitWindow): NSView = cast[NSView](w.mNativeView) proc initFullscreen*(w: AppkitWindow) = w.initCommon(newRect(0, 0, 800, 600)) -method init*(w: AppkitWindow, r: view.Rect) = +method init*(w: AppkitWindow, _: GraphicsContext, r: view.Rect) = w.initCommon(r) proc newFullscreenAppkitWindow(): AppkitWindow = @@ -229,7 +228,7 @@ proc newFullscreenAppkitWindow(): AppkitWindow = proc newAppkitWindow(r: view.Rect): AppkitWindow = result.new() - result.init(r) + result.init(result, r) newWindow = proc(r: view.Rect): Window = result = newAppkitWindow(r) @@ -242,15 +241,13 @@ method drawWindow(w: AppkitWindow) = let s = w.nativeView.bounds.size w.onResize(newSize(s.width, s.height)) - let c = w.renderingContext + template c: untyped = w.gfx c.gl.clear(c.gl.COLOR_BUFFER_BIT or c.gl.STENCIL_BUFFER_BIT or c.gl.DEPTH_BUFFER_BIT) - let oldContext = setCurrentContext(c) c.withTransform ortho(0, w.frame.width, w.frame.height, 0, -1, 1): procCall w.Window.drawWindow() let nv = w.nativeView {.emit: "[[`nv` openGLContext] flushBuffer];".} - setCurrentContext(oldContext) proc markNeedsDisplayAux(w: AppkitWindow) = let nv = w.nativeView diff --git a/nimx/private/windows/emscripten_window.nim b/nimx/private/windows/emscripten_window.nim index bce8867ff..23f144fb8 100644 --- a/nimx/private/windows/emscripten_window.nim +++ b/nimx/private/windows/emscripten_window.nim @@ -7,7 +7,6 @@ import nimx/private/js_vk_map type EmscriptenWindow* = ref object of Window ctx: EMSCRIPTEN_WEBGL_CONTEXT_HANDLE - renderingContext: GraphicsContext canvasId: string textInputActive: bool @@ -266,8 +265,6 @@ proc onContextLost(eventType: cint, reserved: pointer, userData: pointer): EM_BO """) proc initCommon(w: EmscriptenWindow, r: view.Rect) = - procCall init(w.Window, r) - let id = EM_ASM_INT(""" if (window.__nimx_canvas_id === undefined) { window.__nimx_canvas_id = 0; @@ -314,7 +311,9 @@ proc initCommon(w: EmscriptenWindow, r: view.Rect) = if w.ctx <= 0: raise newException(Exception, "Could not create WebGL context: " & $w.ctx) discard emscripten_webgl_make_context_current(w.ctx) - w.renderingContext = newGraphicsContext() + w.gfx = newGraphicsContext() + + procCall init(w.Window, w.gfx, r) const docID = "#document" discard emscripten_set_mousedown_callback(docID, cast[pointer](w), 0, onMouseDown) @@ -349,7 +348,7 @@ proc initFullscreen*(w: EmscriptenWindow) = getDocumentSize(iw, ih) w.initCommon(newRect(0, 0, iw, ih)) -method init*(w: EmscriptenWindow, r: view.Rect) = +method init*(w: EmscriptenWindow, gfx: GraphicsContext, r: view.Rect) = w.initCommon(r) proc newFullscreenEmscriptenWindow*(canvasId = ""): EmscriptenWindow = @@ -360,7 +359,7 @@ proc newFullscreenEmscriptenWindow*(canvasId = ""): EmscriptenWindow = proc newEmscriptenWindow*(r: view.Rect, canvasId = ""): EmscriptenWindow = result.new() result.canvasId = canvasId - result.init(r) + result.init(result.gfx, r) newWindow = proc(r: view.Rect): Window = result = newEmscriptenWindow(r) @@ -377,12 +376,10 @@ newFullscreenWindowWithNative = proc(handle: pointer): Window = result = newFullscreenEmscriptenWindow(canvasId) method drawWindow(w: EmscriptenWindow) = - let c = w.renderingContext - let oldContext = setCurrentContext(c) + template c: untyped = w.gfx c.withTransform ortho(0, w.frame.width, w.frame.height, 0, -1, 1): procCall w.Window.drawWindow() - setCurrentContext(oldContext) method onResize*(w: EmscriptenWindow, newSize: Size) = w.pixelRatio = screenScaleFactor() diff --git a/nimx/private/windows/js_canvas_window.nim b/nimx/private/windows/js_canvas_window.nim index d92585217..35535ca81 100644 --- a/nimx/private/windows/js_canvas_window.nim +++ b/nimx/private/windows/js_canvas_window.nim @@ -7,7 +7,6 @@ import nimx/[ abstract_window, system_logger, view, context, matrixes, app, import nimx/private/js_vk_map type JSCanvasWindow* = ref object of Window - renderingContext: GraphicsContext canvas: Element method fullscreenAvailable*(w: JSCanvasWindow): bool = @@ -253,8 +252,8 @@ proc initWithCanvas*(w: JSCanvasWindow, canvas: Element) = } """.} w.canvas = canvas - procCall w.Window.init(newRect(0, 0, width, height)) - w.renderingContext = newGraphicsContext(canvas) + w.gfx = newGraphicsContext(canvas) + procCall w.Window.init(w.gfx, newRect(0, 0, width, height)) w.setupEventHandlersForCanvas(canvas) @@ -286,7 +285,7 @@ proc newJSCanvasWindow*(canvasId: string): JSCanvasWindow = proc newJSCanvasWindow*(r: Rect): JSCanvasWindow = result.new() - result.init(r) + result.init(result.gfx, r) proc newJSWindowByFillingBrowserWindow*(): JSCanvasWindow = result.new() @@ -298,7 +297,7 @@ newWindow = proc(r: view.Rect): Window = newFullscreenWindow = proc(): Window = result = newJSWindowByFillingBrowserWindow() -method init*(w: JSCanvasWindow, r: Rect) = +method init*(w: JSCanvasWindow, _: GraphicsContext, r: Rect) = let canvas = document.createElement("canvas") let width = r.width let height = r.height @@ -310,14 +309,11 @@ method init*(w: JSCanvasWindow, r: Rect) = w.initWithCanvas(canvas) method drawWindow*(w: JSCanvasWindow) = - let c = w.renderingContext - let oldContext = setCurrentContext(c) - c.withTransform ortho(0, w.frame.width, w.frame.height, 0, -1, 1): + w.gfx.withTransform ortho(0, w.frame.width, w.frame.height, 0, -1, 1): procCall w.Window.drawWindow() - setCurrentContext(oldContext) method onResize*(w: JSCanvasWindow, newSize: Size) = - w.renderingContext.gl.viewport(0, 0, GLSizei(newSize.width), GLsizei(newSize.height)) + w.gfx.gl.viewport(0, 0, GLSizei(newSize.width), GLsizei(newSize.height)) procCall w.Window.onResize(newSize) proc startAnimation*() {.deprecated.} = discard diff --git a/nimx/private/windows/sdl_window.nim b/nimx/private/windows/sdl_window.nim index 2200dcd14..8e147318e 100644 --- a/nimx/private/windows/sdl_window.nim +++ b/nimx/private/windows/sdl_window.nim @@ -1,6 +1,6 @@ import sdl2 except Event, Rect, Point -import nimx/[ abstract_window, system_logger, view, context, event, app, screen, +import nimx/[ abstract_window, view, context, event, app, linkage_details, portable_gl ] import nimx/private/sdl_vk_map import opengl @@ -46,7 +46,6 @@ proc initSDLIfNeeded() = type SdlWindow* = ref object of Window impl: WindowPtr sdlGlContext: GlContextPtr - renderingContext: GraphicsContext isFullscreen: bool when defined(ios) or defined(android): @@ -116,9 +115,9 @@ method animationStateChanged*(w: SdlWindow, state: bool) = var defaultWindow: SdlWindow proc flags(w: SdlWindow): cuint= - result = SDL_WINDOW_OPENGL or SDL_WINDOW_RESIZABLE or SDL_WINDOW_ALLOW_HIGHDPI or SDL_WINDOW_HIDDEN + result = SDL_WINDOW_OPENGL or SDL_WINDOW_ALLOW_HIGHDPI or SDL_WINDOW_HIDDEN or SDL_WINDOW_RESIZABLE if w.isFullscreen: - result = result or SDL_WINDOW_FULLSCREEN_DESKTOP + result = result or SDL_WINDOW_FULLSCREEN # else: # result = result or SDL_WINDOW_HIDDEN @@ -187,7 +186,7 @@ proc updatePixelRatio(w: SdlWindow) {.inline.} = w.pixelRatio = w.scaleFactor() w.viewportPixelRatio = w.pixelRatio -proc initSdlWindow(w: SdlWindow, r: view.Rect) = +proc initSdlWindow(w: SdlWindow, gfx: GraphicsContext, r: view.Rect) = initSDLIfNeeded() when defined(ios) or defined(android): w.isFullscreen = true @@ -211,34 +210,41 @@ proc initSdlWindow(w: SdlWindow, r: view.Rect) = if w.sdlGlContext == nil: error "Could not create context!" discard glMakeCurrent(w.impl, w.sdlGlContext) - w.renderingContext = newGraphicsContext() + w.gfx = if gfx.isNil: newGraphicsContext() else: gfx mainApplication().addWindow(w) discard w.impl.setData("__nimx_wnd", cast[pointer](w)) -method init*(w: SdlWindow, r: view.Rect) = - w.initSdlWindow(r) +method init*(w: SdlWindow, gfx: GraphicsContext, r: view.Rect) = + w.initSdlWindow(gfx, r) let r = w.getOsWindowFrame() - procCall w.Window.init(r) + procCall w.Window.init(w.gfx, r) w.onResize(r.size) -proc newFullscreenSdlWindow*(): SdlWindow = +proc newFullscreenSdlWindow*(gfx: GraphicsContext): SdlWindow = initSDLIfNeeded() var displayMode : DisplayMode discard getDesktopDisplayMode(0, displayMode) result.new() - result.init(newRect(0, 0, displayMode.w.Coord, displayMode.h.Coord)) + result.init(gfx, newRect(0, 0, displayMode.w.Coord, displayMode.h.Coord)) -proc newSdlWindow*(r: view.Rect): SdlWindow = +proc newFullscreenSdlWindow*(): SdlWindow = + newFullscreenSdlWindow(nil) + +proc newSdlWindow*(gfx: GraphicsContext, r: view.Rect): SdlWindow = result.new() - result.init(r) + result.init(gfx, r) + +proc newSdlWindow*(r: view.Rect): SdlWindow = + newSdlWindow(nil, r) method show*(w: SdlWindow)= - if w.impl.isNil: - w.initSdlWindow(w.frame) - w.setFrameOrigin zeroPoint + assert not w.impl.isNil + # if w.impl.isNil: + # w.initSdlWindow(w.frame) + # w.setFrameOrigin zeroPoint w.impl.showWindow() w.impl.raiseWindow() @@ -251,15 +257,20 @@ method hide*(w: SdlWindow)= w.impl.destroyWindow() w.impl = nil w.sdlGlContext = nil - w.renderingContext = nil + w.gfx = nil -newWindow = proc(r: view.Rect): Window = +newWindow = proc(r: Rect): Window = result = newSdlWindow(r) result.show() - +newWindowWithGfxContext = proc(gfx: GraphicsContext, r: Rect): Window = + result = newSdlWindow(gfx, r) + result.show() newFullscreenWindow = proc(): Window = result = newFullscreenSdlWindow() result.show() +newFullscreenWindowWithGfxContext = proc(gfx: GraphicsContext): Window = + result = newFullscreenSdlWindow(gfx) + result.show() method `title=`*(w: SdlWindow, t: string) = w.impl.setTitle(t) @@ -267,7 +278,7 @@ method `title=`*(w: SdlWindow, t: string) = method title*(w: SdlWindow): string = $w.impl.getTitle() method draw*(w: SdlWindow, r: Rect) = - let c = currentContext() + template c: untyped = w.gfx let gl = c.gl if w.mActiveBgColor != w.backgroundColor: gl.clearColor(w.backgroundColor.r, w.backgroundColor.g, w.backgroundColor.b, w.backgroundColor.a) @@ -278,12 +289,10 @@ method draw*(w: SdlWindow, r: Rect) = method drawWindow(w: SdlWindow) = discard glMakeCurrent(w.impl, w.sdlGlContext) - let c = w.renderingContext - let oldContext = setCurrentContext(c) + template c: untyped = w.gfx c.withTransform ortho(0, w.frame.width, w.frame.height, 0, -1, 1): procCall w.Window.drawWindow() w.impl.glSwapWindow() # Swap the front and back frame buffers (double buffering) - setCurrentContext(oldContext) proc windowFromSDLEvent[T](event: T): SdlWindow = let sdlWndId = event.windowID diff --git a/nimx/private/windows/winapi_window.nim b/nimx/private/windows/winapi_window.nim index 41bdbc017..a905e3e82 100644 --- a/nimx/private/windows/winapi_window.nim +++ b/nimx/private/windows/winapi_window.nim @@ -16,7 +16,6 @@ type WinAPiWindow* = ref object of Window hwnd: HWND hglrc: HGLRC hDC: HDC - renderingContext: GraphicsContext method `fullscreen=`*(w: WinAPiWindow, v: bool) = raise newException(OSError, "Not implemented yet") @@ -77,19 +76,19 @@ proc setUpContext(w: WinAPiWindow)= if res == 0: warn "Can't make current ", res, " context ", w.hglrc - w.renderingContext = newGraphicsContext() - if w.renderingContext.isNil: + w.gfx = newGraphicsContext() + if w.gfx.isNil: warn "Can't create GraphicsContext " else: info "GraphicsContext created!" # discard ReleaseDC(w.hwnd, w.hDC) -method init*(w: WinAPiWindow, r: types.Rect)= - procCall w.Window.init(r) +method init*(w: WinAPiWindow, _: GraphicsContext, r: types.Rect)= + procCall w.Window.init(gfx, r) method draw*(w: WinAPiWindow, r: types.Rect) = - let c = currentContext() + template c: untyped = w.gfx let gl = c.gl if w.mActiveBgColor != w.backgroundColor: gl.clearColor(w.backgroundColor.r, w.backgroundColor.g, w.backgroundColor.b, w.backgroundColor.a) @@ -100,13 +99,11 @@ method draw*(w: WinAPiWindow, r: types.Rect) = gl.stencilMask(0x00) method drawWindow(w: WinAPiWindow) = - let c = w.renderingContext - let oldContext = setCurrentContext(c) + template c: untyped = w.gfx c.withTransform ortho(0, w.frame.width, w.frame.height, 0, -1, 1): procCall w.Window.drawWindow() if SwapBuffers(w.hDC) == 0: warn "SwapBuffers failed" - setCurrentContext(oldContext) method onResize*(w: WinAPiWindow, newSize: Size) = w.pixelRatio = screenScaleFactor() @@ -115,7 +112,7 @@ method onResize*(w: WinAPiWindow, newSize: Size) = proc newWinApiWindow(r: types.Rect): WinAPiWindow= result.new() - result.init(r) + result.init(gfx, r) registerWinApinClass() if defaultWindow.isNil: defaultWindow = result diff --git a/nimx/private/windows/x11_window.nim b/nimx/private/windows/x11_window.nim index 8c5072bd0..92708f85f 100644 --- a/nimx/private/windows/x11_window.nim +++ b/nimx/private/windows/x11_window.nim @@ -13,16 +13,15 @@ type X11Window = ref object of Window xdisplay: PDisplay xwindow: x.Window - renderingContext: GraphicsContext var defaultDisplay: PDisplay var allWindows {.threadvar.}: seq[X11Window] proc newXWindow(d: PDisplay, w: x.Window, r: Rect): X11Window = result = X11Window(xdisplay: d, xwindow: w) - result.renderingContext = newGraphicsContext() + result.gfx = newGraphicsContext() allWindows.add(result) - result.init(r) + result.init(gfx, r) mainApplication().addWindow(result) proc chooseVisual(d: PDisplay, screenId: cint): PXVisualInfo = @@ -234,7 +233,7 @@ template runApplication*(initCode: typed) = runForever() method draw*(w: X11Window, r: Rect) = - let c = currentContext() + template c: untyped = w.gfx let gl = c.gl if w.mActiveBgColor != w.backgroundColor: gl.clearColor(w.backgroundColor.r, w.backgroundColor.g, w.backgroundColor.b, w.backgroundColor.a) @@ -245,13 +244,11 @@ method draw*(w: X11Window, r: Rect) = method drawWindow(w: X11Window) = # discard glMakeCurrent(w.impl, w.sdlGlContext) - let c = w.renderingContext - let oldContext = setCurrentContext(c) + template c: untyped = w.gfx c.withTransform ortho(0, w.frame.width, w.frame.height, 0, -1, 1): procCall w.Window.drawWindow() glXSwapBuffers(w.xdisplay, w.xwindow) # w.impl.glSwapWindow() # Swap the front and back frame buffers (double buffering) - setCurrentContext(oldContext) proc scaleFactor(w: X11Window): float = var dpi = 96.0 diff --git a/nimx/progress_indicator.nim b/nimx/progress_indicator.nim index 803034576..4cd1a1728 100644 --- a/nimx/progress_indicator.nim +++ b/nimx/progress_indicator.nim @@ -3,7 +3,7 @@ export view import composition import animation -import abstract_window +import window import times import math @@ -67,19 +67,20 @@ void compose() { } """ -method init(v: ProgressIndicator, r: Rect) = - procCall v.View.init(r) +method init(v: ProgressIndicator, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.animation = newAnimation() v.animation.finished = true v.animation.onAnimate = proc(p: float) = v.setNeedsDisplay() method draw*(v: ProgressIndicator, r: Rect) = + template c: untyped = v.gfx if v.mIndeterminate: - indeterminateComposition.draw v.bounds: + draw c, indeterminateComposition, v.bounds: setUniform("uPosition", float32(epochTime() mod 1.0)) else: - piComposition.draw v.bounds: + draw c, piComposition, v.bounds: setUniform("uPosition", v.mValue) proc `value=`*(v: ProgressIndicator, p: Coord) = diff --git a/nimx/property_editors/autoresizing_mask_editor.nim b/nimx/property_editors/autoresizing_mask_editor.nim index 5510005dc..ff2f158fe 100644 --- a/nimx/property_editors/autoresizing_mask_editor.nim +++ b/nimx/property_editors/autoresizing_mask_editor.nim @@ -2,20 +2,21 @@ import nimx/property_visitor import nimx/numeric_text_field import nimx/popup_button import nimx/linear_layout +import nimx/context import nimx/property_editors/propedit_registry import variant -proc newAutoresizingMaskPropertyView(setter: proc(s: set[AutoresizingFlag]), getter: proc(): set[AutoresizingFlag]): PropertyEditorView = - result = PropertyEditorView.new(newRect(0, 0, 208, editorRowHeight)) +proc newAutoresizingMaskPropertyView(gfx: GraphicsContext, setter: proc(s: set[AutoresizingFlag]), getter: proc(): set[AutoresizingFlag]): PropertyEditorView = + result = PropertyEditorView.new(gfx, newRect(0, 0, 208, editorRowHeight)) - let horLayout = newHorizontalLayout(newRect(0, 0, 208, editorRowHeight)) + let horLayout = newHorizontalLayout(gfx, newRect(0, 0, 208, editorRowHeight)) horLayout.autoresizingMask = {afFlexibleWidth, afFlexibleMaxY} result.addSubview(horLayout) var val = getter() - let horEdit = PopupButton.new(newRect(0, 0, 40, editorRowHeight)) + let horEdit = PopupButton.new(gfx, newRect(0, 0, 40, editorRowHeight)) horEdit.items = @["flexible left", "flexible width", "flexible right"] horEdit.onAction do(): var newFlag = afFlexibleMaxX @@ -35,7 +36,7 @@ proc newAutoresizingMaskPropertyView(setter: proc(s: set[AutoresizingFlag]), get horLayout.addSubview(horEdit) - let vertEdit = PopupButton.new(newRect(40, 0, 40, editorRowHeight)) + let vertEdit = PopupButton.new(gfx, newRect(40, 0, 40, editorRowHeight)) vertEdit.items = @["flexible top", "flexible height", "flexible bottom"] vertEdit.selectedIndex = 0 vertEdit.onAction do(): diff --git a/nimx/property_editors/propedit_registry.nim b/nimx/property_editors/propedit_registry.nim index 033a476de..5f4e89ed9 100644 --- a/nimx/property_editors/propedit_registry.nim +++ b/nimx/property_editors/propedit_registry.nim @@ -3,6 +3,7 @@ import nimx/view import nimx/text_field import nimx/font import nimx/property_visitor +import nimx/context import variant @@ -10,12 +11,13 @@ type PropertyEditorView* = ref object of View onChange*: proc() changeInspector*: proc() - PropertyEditorCreatorWO*[T] = proc(editedObject: Variant, setter: proc(s: T), getter: proc(): T): PropertyEditorView - PropertyEditorCreator*[T] = proc(setter: proc(s: T), getter: proc(): T): PropertyEditorView + PropertyEditorCreatorWO*[T] = proc(gfx: GraphicsContext, editedObject: Variant, setter: proc(s: T), getter: proc(): T): PropertyEditorView + PropertyEditorCreator*[T] = proc(gfx: GraphicsContext, setter: proc(s: T), getter: proc(): T): PropertyEditorView + +var propEditors = initTable[TypeId, proc(gfx: GraphicsContext, editedObject: Variant, v: Variant): PropertyEditorView]() -var propEditors = initTable[TypeId, proc(editedObject: Variant, v: Variant): PropertyEditorView]() proc registerPropertyEditorAUX[T, C](createView: C) = - propEditors[getTypeId(SetterAndGetter[T])] = proc(n: Variant, v: Variant): PropertyEditorView = + propEditors[getTypeId(SetterAndGetter[T])] = proc(gfx: GraphicsContext, n: Variant, v: Variant): PropertyEditorView = let sng = v.get(SetterAndGetter[T]) var r: PropertyEditorView proc setterAUX(s: T) = @@ -23,9 +25,9 @@ proc registerPropertyEditorAUX[T, C](createView: C) = if not r.isNil and not r.onChange.isNil: r.onChange() when C is PropertyEditorCreatorWO: - r = createView(n, setterAUX, sng.getter) + r = createView(gfx, n, setterAUX, sng.getter) else: - r = createView(setterAUX, sng.getter) + r = createView(gfx, setterAUX, sng.getter) result = r proc registerPropertyEditor*[T](createView: PropertyEditorCreatorWO[T]) = @@ -34,16 +36,10 @@ proc registerPropertyEditor*[T](createView: PropertyEditorCreatorWO[T]) = proc registerPropertyEditor*[T](createView: PropertyEditorCreator[T]) = registerPropertyEditorAUX[T, PropertyEditorCreator[T]](createView) -var gEditorFont: Font - -proc editorFont*(): Font = - if gEditorFont.isNil: gEditorFont = systemFontOfSize(14) - result = gEditorFont - const editorRowHeight* = 16 -template createEditorAUX(r: Rect) = - let editor = creator(editedObject, v) +template createEditorAUX(gfx: GraphicsContext, r: Rect) = + let editor = creator(gfx, editedObject, v) editor.name = "editor" editor.setFrameOrigin(r.origin) var sz = newSize(r.size.width, editor.frame.height) @@ -58,28 +54,28 @@ template createEditorAUX(r: Rect) = editor.changeInspector = changeInspectorCallback editor.onChange = onChange -proc propertyEditorForProperty*(editedObject: Variant, title: string, v: Variant, onChange, changeInspectorCallback: proc() = nil): View = +proc propertyEditorForProperty*(gfx: GraphicsContext, editedObject: Variant, title: string, v: Variant, onChange, changeInspectorCallback: proc() = nil): View = let creator = propEditors.getOrDefault(v.typeId) - result = View.new(newRect(0, 0, 328, editorRowHeight)) + result = View.new(gfx, newRect(0, 0, 328, editorRowHeight)) result.name = "'" & title & "'" result.autoresizingMask = {afFlexibleWidth, afFlexibleMaxY} - let label = newLabel(newRect(0, 0, 100, editorRowHeight)) + let label = newLabel(gfx, newRect(0, 0, 100, editorRowHeight)) label.textColor = blackColor() label.name = "label" label.text = title & ":" - label.font = editorFont() + label.font = systemFontOfSize(gfx.fontCtx, 14.0) result.addSubview(label) if creator.isNil: label.text = title & " - Unknown property" else: - createEditorAUX(newRect(label.frame.width, 0, result.bounds.width - label.frame.width, result.bounds.height)) + createEditorAUX(gfx, newRect(label.frame.width, 0, result.bounds.width - label.frame.width, result.bounds.height)) -proc propertyEditorForProperty*(editedObject: Variant, v: Variant, changeInspectorCallback: proc() = nil): View = +proc propertyEditorForProperty*(gfx: GraphicsContext, editedObject: Variant, v: Variant, changeInspectorCallback: proc() = nil): View = let creator = propEditors.getOrDefault(v.typeId) - result = View.new(newRect(0, 0, 228, editorRowHeight)) + result = View.new(gfx, newRect(0, 0, 228, editorRowHeight)) result.autoresizingMask = {afFlexibleWidth, afFlexibleMaxY} if creator.isNil: - discard result.newLabel(newPoint(100, 0), newSize(128, editorRowHeight), "Unknown") + discard result.newLabel(gfx, newPoint(100, 0), newSize(128, editorRowHeight), "Unknown") else: const onChange: proc() = nil - createEditorAUX(newRect(0,0, result.bounds.width, result.bounds.height)) \ No newline at end of file + createEditorAUX(gfx, newRect(0,0, result.bounds.width, result.bounds.height)) \ No newline at end of file diff --git a/nimx/property_editors/standard_editors.nim b/nimx/property_editors/standard_editors.nim index 7cfe73505..0248f9cbd 100644 --- a/nimx/property_editors/standard_editors.nim +++ b/nimx/property_editors/standard_editors.nim @@ -33,11 +33,11 @@ template toStr(v: SomeInteger): string = $v template fromStr(v: string, t: var SomeFloat) = t = v.parseFloat() template fromStr(v: string, t: var SomeInteger) = t = type(t)(v.parseInt()) -proc newScalarPropertyView[T](setter: proc(s: T), getter: proc(): T): PropertyEditorView = - result = PropertyEditorView.new(newRect(0, 0, 208, editorRowHeight)) - let tf = newNumericTextField(newRect(0, 0, 208, editorRowHeight)) +proc newScalarPropertyView[T](gfx: GraphicsContext, setter: proc(s: T), getter: proc(): T): PropertyEditorView = + result = PropertyEditorView.new(gfx, newRect(0, 0, 208, editorRowHeight)) + let tf = newNumericTextField(gfx, newRect(0, 0, 208, editorRowHeight)) tf.autoresizingMask = {afFlexibleWidth, afFlexibleMaxY} - tf.font = editorFont() + tf.font = systemFontOfSize(gfx.fontCtx, 14.0) when T is SomeFloat: tf.text = toStr(getter(), tf.precision) else: @@ -52,22 +52,22 @@ proc newScalarPropertyView[T](setter: proc(s: T), getter: proc(): T): PropertyEd discard result.addSubview(tf) -proc newTextPropertyView(setter: proc(s: string), getter: proc(): string): PropertyEditorView = - result = PropertyEditorView.new(newRect(0, 0, 208, editorRowHeight)) - let textField = newTextField(newRect(0, 0, 208, editorRowHeight)) +proc newTextPropertyView(gfx: GraphicsContext, setter: proc(s: string), getter: proc(): string): PropertyEditorView = + result = PropertyEditorView.new(gfx, newRect(0, 0, 208, editorRowHeight)) + let textField = newTextField(gfx, newRect(0, 0, 208, editorRowHeight)) textField.autoresizingMask = {afFlexibleWidth, afFlexibleMaxY} - textField.font = editorFont() + textField.font = systemFontOfSize(gfx.fontCtx, 14.0) textField.text = getter() textField.onAction do(): setter(textField.text) result.addSubview(textField) -proc newVecPropertyView[T](setter: proc(s: T), getter: proc(): T): PropertyEditorView = - result = PropertyEditorView.new(newRect(0, 0, 208, editorRowHeight)) +proc newVecPropertyView[T](gfx: GraphicsContext, setter: proc(s: T), getter: proc(): T): PropertyEditorView = + result = PropertyEditorView.new(gfx, newRect(0, 0, 208, editorRowHeight)) const vecLen = high(T) + 1 - let horLayout = newHorizontalLayout(newRect(0, 0, 208, editorRowHeight)) + let horLayout = newHorizontalLayout(gfx, newRect(0, 0, 208, editorRowHeight)) horLayout.autoresizingMask = {afFlexibleWidth, afFlexibleMaxY} result.addSubview(horLayout) @@ -82,9 +82,9 @@ proc newVecPropertyView[T](setter: proc(s: T), getter: proc(): T): PropertyEdito let val = getter() for i in 0 ..< vecLen: - let textField = newNumericTextField(zeroRect) + let textField = newNumericTextField(gfx, zeroRect) textField.name = "#" & $i - textField.font = editorFont() + textField.font = systemFontOfSize(gfx.fontCtx, 14.0) textField.text = toStr(val[i], textField.precision) textField.onAction complexSetter horLayout.addSubview(textField) @@ -101,24 +101,24 @@ method viewShouldResignFirstResponder*(t: ColorComponentTextField, newFirstRespo result = procCall t.NumericTextField.viewShouldResignFirstResponder(newFirstResponder) if result and not t.onResignFirstResponder.isNil: t.onResignFirstResponder() -proc newColorPropertyView(setter: proc(s: Color), getter: proc(): Color): PropertyEditorView = - result = PropertyEditorView.new(newRect(0, 0, 208, editorRowHeight)) +proc newColorPropertyView(gfx: GraphicsContext, setter: proc(s: Color), getter: proc(): Color): PropertyEditorView = + result = PropertyEditorView.new(gfx, newRect(0, 0, 208, editorRowHeight)) const vecLen = 3 + 1 var beginColorPicker: proc() var colorInColorPickerSelected: proc(pc: Color) - let colorView = Button.new(newRect(0, 0, editorRowHeight, editorRowHeight)) + let colorView = Button.new(gfx, newRect(0, 0, editorRowHeight, editorRowHeight)) colorView.backgroundColor = getter() result.addSubview(colorView) colorView.hasBezel = false colorView.onAction beginColorPicker - let horLayout = newHorizontalLayout(newRect(editorRowHeight, 0, result.bounds.width - editorRowHeight, editorRowHeight)) + let horLayout = newHorizontalLayout(gfx, newRect(editorRowHeight, 0, result.bounds.width - editorRowHeight, editorRowHeight)) horLayout.autoresizingMask = {afFlexibleWidth, afFlexibleMaxY} result.addSubview(horLayout) - let colorPicker = sharedColorPicker() + let colorPicker = newColorPickerView(gfx) proc complexSetter() = try: let c = newColor( @@ -157,8 +157,8 @@ proc newColorPropertyView(setter: proc(s: Color), getter: proc(): Color): Proper template toVector(c: Color): Vector4 = newVector4(c.r, c.g, c.b, c.a) for i in 0 ..< vecLen: - let textField = ColorComponentTextField.new(zeroRect) - textField.font = editorFont() + let textField = ColorComponentTextField.new(gfx, zeroRect) + textField.font = systemFontOfSize(gfx.fontCtx, 14.0) textField.text = toStr(getter().toVector[i], textField.precision) textField.onAction complexSetter textField.onBecomeFirstResponder = beginColorPicker @@ -166,24 +166,27 @@ proc newColorPropertyView(setter: proc(s: Color), getter: proc(): Color): Proper textField.continuous = true horLayout.addSubview(textField) -proc newRectPropertyView(setter: proc(s: Rect), getter: proc(): Rect): PropertyEditorView = +proc newRectPropertyView(gfx: GraphicsContext, setter: proc(s: Rect), getter: proc(): Rect): PropertyEditorView = newVecPropertyView( + gfx, proc(v: Vector4) = setter(newRect(v.x, v.y, v.z, v.w)), proc(): Vector4 = let s = getter() result = newVector4(s.x, s.y, s.width, s.height) ) -proc newSizePropertyView(setter: proc(s: Size), getter: proc(): Size): PropertyEditorView = +proc newSizePropertyView(gfx: GraphicsContext, setter: proc(s: Size), getter: proc(): Size): PropertyEditorView = newVecPropertyView( + gfx, proc(v: Vector2) = setter(newSize(v.x, v.y)), proc(): Vector2 = let s = getter() result = newVector2(s.width, s.height) ) -proc newPointPropertyView(setter: proc(s: Point), getter: proc(): Point): PropertyEditorView = +proc newPointPropertyView(gfx: GraphicsContext, setter: proc(s: Point), getter: proc(): Point): PropertyEditorView = newVecPropertyView( + gfx, proc(v: Vector2) = setter(newPoint(v.x, v.y)), proc(): Vector2 = let s = getter() @@ -191,24 +194,24 @@ proc newPointPropertyView(setter: proc(s: Point), getter: proc(): Point): Proper ) when not defined(android) and not defined(ios): - proc newImagePropertyView(setter: proc(s: Image), getter: proc(): Image): PropertyEditorView = + proc newImagePropertyView(gfx: GraphicsContext, setter: proc(s: Image), getter: proc(): Image): PropertyEditorView = var loadedImage = getter() var pv: PropertyEditorView if not loadedImage.isNil: let previewSize = 48.0 - pv = PropertyEditorView.new(newRect(0, 0, 208, editorRowHeight + 6 + previewSize)) + pv = PropertyEditorView.new(gfx, newRect(0, 0, 208, editorRowHeight + 6 + previewSize)) - let imgButton = newImageButton(pv, newPoint(0, editorRowHeight + 3), newSize(previewSize, previewSize), loadedImage) + let imgButton = newImageButton(pv, gfx, newPoint(0, editorRowHeight + 3), newSize(previewSize, previewSize), loadedImage) imgButton.onAction do(): - let imgPreview = newImagePreview(newRect(0, 0, 200, 200), loadedImage) + let imgPreview = newImagePreview(gfx, newRect(0, 0, 200, 200), loadedImage) imgPreview.popupAtPoint(pv, newPoint(-10, 0)) - let label = newLabel(newRect(previewSize + 5, editorRowHeight + 5 + editorRowHeight, 100, 15)) + let label = newLabel(gfx, newRect(previewSize + 5, editorRowHeight + 5 + editorRowHeight, 100, 15)) label.text = "S: " & $int(loadedImage.size.width) & " x " & $int(loadedImage.size.height) label.textColor = newGrayColor(0.9) pv.addSubview(label) - let removeButton = Button.new(newRect(previewSize + 5, editorRowHeight + 3, editorRowHeight, editorRowHeight)) + let removeButton = Button.new(gfx, newRect(previewSize + 5, editorRowHeight + 3, editorRowHeight, editorRowHeight)) removeButton.title = "-" pv.addSubview(removeButton) removeButton.onAction do(): @@ -216,9 +219,9 @@ when not defined(android) and not defined(ios): if not pv.changeInspector.isNil: pv.changeInspector() else: - pv = PropertyEditorView.new(newRect(0, 0, 208, editorRowHeight)) + pv = PropertyEditorView.new(gfx, newRect(0, 0, 208, editorRowHeight)) - let b = Button.new(newRect(0, 0, 208, editorRowHeight)) + let b = Button.new(gfx, newRect(0, 0, 208, editorRowHeight)) b.autoresizingMask = {afFlexibleWidth, afFlexibleMaxY} b.title = "Open image..." b.onAction do(): @@ -250,17 +253,17 @@ when not defined(android) and not defined(ios): registerPropertyEditor(newImagePropertyView) -proc newBoolPropertyView(setter: proc(s: bool), getter: proc(): bool): PropertyEditorView = - let pv = PropertyEditorView.new(newRect(0, 0, 208, editorRowHeight)) - let cb = newCheckbox(newRect(0, 0, editorRowHeight, editorRowHeight)) +proc newBoolPropertyView(gfx: GraphicsContext, setter: proc(s: bool), getter: proc(): bool): PropertyEditorView = + let pv = PropertyEditorView.new(gfx, newRect(0, 0, 208, editorRowHeight)) + let cb = newCheckbox(gfx, newRect(0, 0, editorRowHeight, editorRowHeight)) cb.value = if getter(): 1 else: 0 cb.onAction do(): setter(cb.boolValue) result = pv result.addSubview(cb) -proc newEnumPropertyView(setter: proc(s: EnumValue), getter: proc(): EnumValue): PropertyEditorView = - let pv = PropertyEditorView.new(newRect(0, 0, 208, editorRowHeight)) +proc newEnumPropertyView(gfx: GraphicsContext, setter: proc(s: EnumValue), getter: proc(): EnumValue): PropertyEditorView = + let pv = PropertyEditorView.new(gfx, newRect(0, 0, 208, editorRowHeight)) var val = getter() var items = newSeq[string]() for k, v in val.possibleValues: @@ -274,6 +277,7 @@ proc newEnumPropertyView(setter: proc(s: EnumValue), getter: proc(): EnumValue): break var enumChooser = newPopupButton(pv, + gfx, newPoint(0.0, 0.0), newSize(208, editorRowHeight), items, startVal) @@ -287,10 +291,10 @@ proc newEnumPropertyView(setter: proc(s: EnumValue), getter: proc(): EnumValue): result = pv -proc newScalarSeqPropertyView[T](setter: proc(s: seq[T]), getter: proc(): seq[T]): PropertyEditorView = +proc newScalarSeqPropertyView[T](gfx: GraphicsContext, setter: proc(s: seq[T]), getter: proc(): seq[T]): PropertyEditorView = var val = getter() var height = val.len() * 26 + 26 - let pv = PropertyEditorView.new(newRect(0, 0, 208, height.Coord)) + let pv = PropertyEditorView.new(gfx, newRect(0, 0, 208, height.Coord)) proc onValChange() = setter(val) @@ -304,8 +308,8 @@ proc newScalarSeqPropertyView[T](setter: proc(s: seq[T]), getter: proc(): seq[T] for i in 0 ..< val.len: closureScope: let index = i - let tf = newNumericTextField(newRect(0.Coord, y, 150, editorRowHeight)) - tf.font = editorFont() + let tf = newNumericTextField(gfx, newRect(0.Coord, y, 150, editorRowHeight)) + tf.font = systemFontOfSize(gfx.fontCtx, 14.0) pv.addSubview(tf) tf.text = toStr(val[i], tf.precision) tf.onAction do(): @@ -313,7 +317,7 @@ proc newScalarSeqPropertyView[T](setter: proc(s: seq[T]), getter: proc(): seq[T] fromStr(tf.text, val[index]) onValChange() - let removeButton = Button.new(newRect(153, y, editorRowHeight, editorRowHeight)) + let removeButton = Button.new(gfx, newRect(153, y, editorRowHeight, editorRowHeight)) removeButton.title = "-" pv.addSubview(removeButton) removeButton.onAction do(): @@ -322,7 +326,7 @@ proc newScalarSeqPropertyView[T](setter: proc(s: seq[T]), getter: proc(): seq[T] y += 18 - let addButton = Button.new(newRect(153, y, editorRowHeight, editorRowHeight)) + let addButton = Button.new(gfx, newRect(153, y, editorRowHeight, editorRowHeight)) addButton.title = "+" pv.addSubview(addButton) addButton.onAction do(): @@ -332,10 +336,10 @@ proc newScalarSeqPropertyView[T](setter: proc(s: seq[T]), getter: proc(): seq[T] result = pv # proc newSeqPropertyView[I: static[int], T](setter: proc(s: seq[TVector[I, T]]), getter: proc(): seq[TVector[I, T]]): PropertyEditorView = -proc newSeqPropertyView[T](setter: proc(s: seq[T]), getter: proc(): seq[T]): PropertyEditorView = +proc newSeqPropertyView[T](gfx: GraphicsContext, setter: proc(s: seq[T]), getter: proc(): seq[T]): PropertyEditorView = var val = getter() var height = val.len() * 26 + 26 - let pv = PropertyEditorView.new(newRect(0, 0, 208, height.Coord)) + let pv = PropertyEditorView.new(gfx, newRect(0, 0, 208, height.Coord)) const vecLen = high(T) + 1 proc onValChange() = @@ -357,8 +361,8 @@ proc newSeqPropertyView[T](setter: proc(s: seq[T]), getter: proc(): seq[T]): Pro for j in 0 ..< vecLen: closureScope: let jIndex = j - let tf = newNumericTextField(newRect(x, y, 35, editorRowHeight)) - tf.font = editorFont() + let tf = newNumericTextField(gfx, newRect(x, y, 35, editorRowHeight)) + tf.font = systemFontOfSize(gfx.fontCtx, 14.0) x += 37 pv.addSubview(tf) tf.text = toStr(vecVal[j], tf.precision) @@ -367,7 +371,7 @@ proc newSeqPropertyView[T](setter: proc(s: seq[T]), getter: proc(): seq[T]): Pro val[index][jIndex] = tf.text.parseFloat() onValChange() - let removeButton = Button.new(newRect(x, y, editorRowHeight, editorRowHeight)) + let removeButton = Button.new(gfx, newRect(x, y, editorRowHeight, editorRowHeight)) removeButton.title = "-" pv.addSubview(removeButton) removeButton.onAction do(): @@ -376,7 +380,7 @@ proc newSeqPropertyView[T](setter: proc(s: seq[T]), getter: proc(): seq[T]): Pro y += editorRowHeight + 2 - let addButton = Button.new(newRect(x, y, editorRowHeight, editorRowHeight)) + let addButton = Button.new(gfx, newRect(x, y, editorRowHeight, editorRowHeight)) addButton.title = "+" pv.addSubview(addButton) addButton.onAction do(): @@ -386,8 +390,8 @@ proc newSeqPropertyView[T](setter: proc(s: seq[T]), getter: proc(): seq[T]): Pro result = pv -proc newFontPropertyView(setter: proc(s: Font), getter: proc(): Font): PropertyEditorView = - result = PropertyEditorView.new(newRect(0, 0, 208, editorRowHeight)) +proc newFontPropertyView(gfx: GraphicsContext, setter: proc(s: Font), getter: proc(): Font): PropertyEditorView = + result = PropertyEditorView.new(gfx, newRect(0, 0, 208, editorRowHeight)) var val = getter() var items = getAvailableFonts() var fontSize = 16.0 @@ -401,14 +405,14 @@ proc newFontPropertyView(setter: proc(s: Font), getter: proc(): Font): PropertyE startVal = i break - var enumChooser = newPopupButton(result, + var enumChooser = newPopupButton(result, gfx, newPoint(0.0, 0.0), newSize(208, editorRowHeight), items, startVal) enumChooser.autoresizingMask = {afFlexibleWidth, afFlexibleMaxY} enumChooser.onAction do(): - let val = newFontWithFace(enumChooser.selectedItem(), fontSize) + let val = newFontWithFace(gfx.fontCtx, enumChooser.selectedItem(), fontSize) setter(val) @@ -440,6 +444,6 @@ template initPropertyEditor*(v: View, eo: untyped, propName: string, property: u visitor.requireGetter = true visitor.flags = { pfEditable } visitor.commit = proc() = - v.addSubview(propertyEditorForProperty(o, visitor.name, visitor.setterAndGetter)) + v.addSubview(propertyEditorForProperty(v.gfx, o, visitor.name, visitor.setterAndGetter)) visitor.visitProperty(propName, property) diff --git a/nimx/render_to_image.nim b/nimx/render_to_image.nim index 19e9ba23a..df4ef6d25 100644 --- a/nimx/render_to_image.nim +++ b/nimx/render_to_image.nim @@ -13,6 +13,7 @@ type ImageRenderTarget* = ref ImageRenderTargetObj ImageRenderTargetObj = object + gfx*: GraphicsContext framebuffer*: FramebufferRef depthbuffer*: RenderbufferRef stencilbuffer*: RenderbufferRef @@ -22,7 +23,7 @@ type needsDepthStencil*: bool proc disposeObj(r: var ImageRenderTargetObj) = - let gl = sharedGL() + template gl: untyped = r.gfx.gl if r.framebuffer != invalidFrameBuffer: gl.deleteFramebuffer(r.framebuffer) r.framebuffer = invalidFrameBuffer @@ -38,7 +39,7 @@ proc dispose*(r: ImageRenderTarget) = disposeObj(r[]) when defined(gcDestructors): proc `=destroy`(r: var ImageRenderTargetObj) = disposeObj(r) -proc newImageRenderTarget*(needsDepthStencil: bool = true): ImageRenderTarget {.inline.} = +proc newImageRenderTarget*(ctx: GraphicsContext, needsDepthStencil: bool = true): ImageRenderTarget {.inline.} = when defined(js): result.new() else: @@ -46,10 +47,11 @@ proc newImageRenderTarget*(needsDepthStencil: bool = true): ImageRenderTarget {. result.new() else: result.new(dispose) + result.gfx = ctx result.needsDepthStencil = needsDepthStencil proc init(rt: ImageRenderTarget, texWidth, texHeight: int16) = - let gl = sharedGL() + template gl: untyped = rt.gfx.gl rt.texWidth = texWidth rt.texHeight = texHeight rt.framebuffer = gl.createFramebuffer() @@ -97,7 +99,7 @@ proc init(rt: ImageRenderTarget, texWidth, texHeight: int16) = gl.bindRenderbuffer(gl.RENDERBUFFER, oldRB) proc resize(rt: ImageRenderTarget, texWidth, texHeight: int16) = - let gl = sharedGL() + template gl: untyped = rt.gfx.gl rt.texWidth = max(rt.texWidth, texWidth) rt.texHeight = max(rt.texHeight, texHeight) @@ -117,7 +119,7 @@ proc resize(rt: ImageRenderTarget, texWidth, texHeight: int16) = proc setImage*(rt: ImageRenderTarget, i: SelfContainedImage) = assert(i.texWidth != 0 and i.texHeight != 0) - let gl = sharedGL() + template gl: untyped = rt.gfx.gl var texCoords: array[4, GLfloat] var texture = i.getTextureQuad(gl, texCoords) if texture.isEmpty: @@ -150,7 +152,7 @@ proc setImage*(rt: ImageRenderTarget, i: SelfContainedImage) = proc beginDraw*(t: ImageRenderTarget, state: var RTIContext) = assert(t.vpW != 0 and t.vpH != 0) - let gl = sharedGL() + template gl: untyped = t.gfx.gl state.framebuffer = gl.boundFramebuffer() state.viewportSize = gl.getViewport() state.bStencil = gl.getParamb(gl.STENCIL_TEST) @@ -171,7 +173,7 @@ proc beginDrawNoClear*(t: ImageRenderTarget, state: var RTIContext) = t.beginDraw(state) proc endDraw*(t: ImageRenderTarget, state: var RTIContext) = - let gl = sharedGL() + template gl: untyped = t.gfx.gl if state.bStencil: gl.enable(gl.STENCIL_TEST) if not state.skipClear: @@ -184,7 +186,7 @@ template draw*(rt: ImageRenderTarget, sci: SelfContainedImage, drawBody: untyped rt.setImage(sci) rt.beginDraw(gfs) - currentContext().withTransform ortho(0, sci.size.width, sci.size.height, 0, -1, 1): + rt.gfx.withTransform ortho(0, sci.size.width, sci.size.height, 0, -1, 1): drawBody rt.endDraw(gfs) @@ -194,11 +196,11 @@ template draw*(rt: ImageRenderTarget, sci: SelfContainedImage, drawBody: untyped if not sci.flipped: sci.flipVertically() -template draw*(sci: SelfContainedImage, drawBody: untyped) = - let rt = newImageRenderTarget() +template draw*(ctx: GraphicsContext, sci: SelfContainedImage, drawBody: untyped) = + let rt = newImageRenderTarget(ctx) rt.draw(sci, drawBody) rt.dispose() -proc draw*(sci: SelfContainedImage, drawProc: proc()) {.deprecated.} = - sci.draw: +proc draw*(ctx: GraphicsContext, sci: SelfContainedImage, drawProc: proc()) {.deprecated.} = + draw ctx, sci: drawProc() diff --git a/nimx/scroll_bar.nim b/nimx/scroll_bar.nim index 03ff7c088..7a1dad481 100644 --- a/nimx/scroll_bar.nim +++ b/nimx/scroll_bar.nim @@ -11,8 +11,8 @@ type ScrollBar* = ref object of Slider trackingPos: Coord # Position of mouse coordinate (x or y depending on orientation) within knob const minKnobSize = 0.05 -method init*(s: ScrollBar, r: Rect) = - procCall s.Slider.init(r) +method init*(s: ScrollBar, gfx: GraphicsContext, r: Rect) = + procCall s.Slider.init(gfx, r) s.mKnobSize = 0.2 proc knobRect(s: ScrollBar): Rect = @@ -33,7 +33,7 @@ proc knobRect(s: ScrollBar): Rect = method draw*(s: ScrollBar, r: Rect) = let bezelRect = s.bounds.inset(1, 1) var radius = min(bezelRect.width, bezelRect.height) / 2 - let c = currentContext() + template c: untyped = s.gfx c.fillColor = newGrayColor(0.85, 0.5) c.strokeColor = newGrayColor(0.5, 0.5) c.strokeWidth = 0.5 diff --git a/nimx/scroll_view.nim b/nimx/scroll_view.nim index fe5b82fa6..e4dbc7353 100644 --- a/nimx/scroll_view.nim +++ b/nimx/scroll_view.nim @@ -1,4 +1,4 @@ -import view, scroll_bar, event, layout_vars +import context, view, scroll_bar, event, layout_vars import kiwi import math export view @@ -7,7 +7,7 @@ import clip_view # [Deprecated old layout] type ScrollView* = ref object of View mContentView: View - clipView: ClipView # [Deprecated old layout] + clipView {.deprecated.} : ClipView mHorizontalScrollBar, mVerticalScrollBar: ScrollBar mOnScrollCallback: proc() constraints: seq[Constraint] @@ -15,22 +15,22 @@ type ScrollView* = ref object of View const scrollBarWidth = 12.Coord -method init*(v: ScrollView, r: Rect) = - procCall v.View.init(r) +method init*(v: ScrollView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) if v.usesNewLayout: # Assume new layout v.xPos = newVariable() v.yPos = newVariable() - v.addSubview(ScrollBar.new(newRect(0, 0, 10, 0))) - v.addSubview(ScrollBar.new(newRect(0, 0, 0, 10))) + v.addSubview(ScrollBar.new(gfx, newRect(0, 0, 10, 0))) + v.addSubview(ScrollBar.new(gfx, newRect(0, 0, 0, 10))) proc onScrollBar(v: ScrollView, sb: ScrollBar) proc onScroll*(v: ScrollView, cb: proc())= v.mOnScrollCallback = cb -proc relayout(v: ScrollView) = # [Deprecated old layout] +proc relayout(v: ScrollView) {.deprecated.} = var cvs = v.bounds.size if not v.mVerticalScrollBar.isNil: @@ -73,20 +73,20 @@ template verticalScrollBar*(v: ScrollView): ScrollBar = v.mVerticalScrollBar proc recalcScrollKnobSizes(v: ScrollView) -proc newScrollView*(r: Rect): ScrollView = # [Deprecated old layout] +proc newScrollView*(gfx: GraphicsContext, r: Rect): ScrollView {.deprecated.} = result.new() result.name = "scrollView" - result.init(r) + result.init(gfx, r) - var sb = ScrollBar.new(newRect(0, r.height - scrollBarWidth, 0, scrollBarWidth)) + var sb = ScrollBar.new(gfx, newRect(0, r.height - scrollBarWidth, 0, scrollBarWidth)) sb.autoresizingMask = {afFlexibleWidth, afFlexibleMinY} result.horizontalScrollBar = sb - sb = ScrollBar.new(newRect(r.width - scrollBarWidth, 0, scrollBarWidth, 0)) + sb = ScrollBar.new(gfx, newRect(r.width - scrollBarWidth, 0, scrollBarWidth, 0)) sb.autoresizingMask = {afFlexibleMinX, afFlexibleHeight} result.verticalScrollBar = sb - result.clipView = newClipView(zeroRect) + result.clipView = newClipView(gfx, zeroRect) result.addSubview(result.clipView) result.relayout() @@ -115,9 +115,9 @@ proc `contentView=`*(v: ScrollView, c: View) = v.clipView.addSubview(c) v.recalcScrollKnobSizes() -proc newScrollView*(v: View): ScrollView = # [Deprecated old layout] +proc newScrollView*(gfx: GraphicsContext, v: View): ScrollView {.deprecated.} = # Create a scrollview by wrapping v into it - result = newScrollView(v.frame) + result = newScrollView(gfx, v.frame) result.autoresizingMask = v.autoresizingMask result.contentView = v #v.autoresizingMask = { afFlexibleMaxX, afFlexibleMaxY } @@ -228,7 +228,7 @@ proc onScrollBar(v: ScrollView, sb: ScrollBar) = v.clipView.setBoundsOrigin(o) -method subviewDidChangeDesiredSize*(v: ScrollView, sub: View, desiredSize: Size) = # [Deprecated old layout] +method subviewDidChangeDesiredSize*(v: ScrollView, sub: View, desiredSize: Size) {.deprecated.} = if not v.usesNewLayout: let cvBounds = v.clipView.bounds var boundsOrigin = cvBounds.origin @@ -245,7 +245,7 @@ method subviewDidChangeDesiredSize*(v: ScrollView, sub: View, desiredSize: Size) v.recalcScrollKnobSizes() v.recalcScrollbarKnobPositions() -proc recalcScrollKnobSizes(v: ScrollView) = # [Deprecated old layout] +proc recalcScrollKnobSizes(v: ScrollView) {.deprecated.} = var cs = v.contentSize if not v.mHorizontalScrollBar.isNil and cs.width != 0.0: v.mHorizontalScrollBar.knobSize = v.bounds.width / cs.width @@ -254,7 +254,7 @@ proc recalcScrollKnobSizes(v: ScrollView) = # [Deprecated old layout] v.mVerticalScrollBar.knobSize = v.bounds.height / cs.height v.mVerticalScrollBar.hidden = v.mVerticalScrollBar.knobSize == 1.0 -method resizeSubviews*(v: ScrollView, oldSize: Size) = # [Deprecated old layout] +method resizeSubviews*(v: ScrollView, oldSize: Size) {.deprecated.} = procCall v.View.resizeSubviews(oldSize) if not v.usesNewLayout: v.recalcScrollKnobSizes() diff --git a/nimx/segmented_control.nim b/nimx/segmented_control.nim index f5ddffe5e..43d958967 100644 --- a/nimx/segmented_control.nim +++ b/nimx/segmented_control.nim @@ -48,20 +48,22 @@ proc `segments=`*(s: SegmentedControl, segs: seq[string]) = template segments*(s: SegmentedControl): seq[string] = s.mSegments -method init*(s: SegmentedControl, r: Rect) = - procCall s.Control.init(r) +method init*(s: SegmentedControl, gfx: GraphicsContext, r: Rect) = + procCall s.Control.init(gfx, r) s.segments = @["hello", "world", "yo"] method updateLayout*(s: SegmentedControl) = s.widthsValid = false proc recalculateSegmentWidths(s: SegmentedControl) = + template fontCtx: untyped = s.gfx.fontCtx + template gl: untyped = s.gfx.gl s.widths.setLen(s.mSegments.len) - let font = systemFont() + let font = systemFont(fontCtx) var totalWidth = 0.Coord for i, seg in s.mSegments: - let w = font.sizeOfString(seg).width + let w = sizeOfString(fontCtx, gl, font, seg).width s.widths[i] = w totalWidth += w s.padding = (s.bounds.width - totalWidth) / s.mSegments.len.Coord @@ -76,10 +78,12 @@ proc recalculateSegmentWidths(s: SegmentedControl) = s.widthsValid = true method draw*(s: SegmentedControl, r: Rect) = + template gfx: untyped = s.gfx + template fontCtx: untyped = gfx.fontCtx if not s.widthsValid: s.recalculateSegmentWidths() - scComposition.draw s.bounds: + draw gfx, scComposition, s.bounds: if s.mSelectedSegment < s.widths.len and s.mSelectedSegment >= 0: setUniform("uSelectedRect", newRect(s.selectedSegmentOffset, 0, s.widths[s.mSelectedSegment] + s.padding, s.bounds.height)) @@ -89,23 +93,22 @@ method draw*(s: SegmentedControl, r: Rect) = setUniform("uSelectedRect", zeroRect) setUniform("uTrackedRect", zeroRect) - let font = systemFont() - let c = currentContext() + let font = systemFont(fontCtx) var r = newRect(0, 0, 0, s.bounds.height) var strSize = newSize(0, font.height) - c.strokeWidth = 0 + gfx.strokeWidth = 0 for i, w in s.widths: if i == s.mSelectedSegment: - c.fillColor = whiteColor() + gfx.fillColor = whiteColor() else: - c.fillColor = blackColor() + gfx.fillColor = blackColor() r.size.width = w + s.padding strSize.width = w - c.drawText(font, strSize.centerInRect(r), s.mSegments[i]) + gfx.drawText(font, strSize.centerInRect(r), s.mSegments[i]) if i != 0 and i != s.mSelectedSegment and i - 1 != s.mSelectedSegment and i != s.trackedSegment and i - 1 != s.trackedSegment: - c.fillColor = newGrayColor(0.78) - c.drawRect(newRect(r.origin.x - 1, 1, 1, r.height - 2)) + gfx.fillColor = newGrayColor(0.78) + gfx.drawRect(newRect(r.origin.x - 1, 1, 1, r.height - 2)) r.origin.x += r.size.width template selectedSegment*(s: SegmentedControl): int = s.mSelectedSegment diff --git a/nimx/serializers.nim b/nimx/serializers.nim index c8003388d..27d171601 100644 --- a/nimx/serializers.nim +++ b/nimx/serializers.nim @@ -1,4 +1,5 @@ import json + type Serializer* = ref object of RootObj curKey*: string @@ -89,6 +90,9 @@ type Deserializer* = ref object of RootObj curKey*: string curIndex*: int +type ContextFree = bool | int8 | int16 | int32 | int64 | uint8 | uint16 | + uint32 | uint64 | float32 | float64 | string | int + # Methods to override method deserialize*(s: Deserializer, v: var bool) {.base.} = abstractCall() @@ -121,20 +125,24 @@ template deserialize*(s: Deserializer, v: var int) = s.deserialize(t) v = int(t) -template deserialize*(s: Deserializer, k: string, v: untyped) = +template deserialize*(s: Deserializer, k: string, v: untyped, gfx: RootRef = nil) = var desv: type(v) - s.deserialize(k, desv) + s.deserialize(k, desv, gfx) v = desv -proc deserialize*[T](s: Deserializer, k: string, v: var T) {.inline.} = +proc deserialize*(s: Deserializer, k: string, v: var ContextFree, gfx: RootRef = nil) {.inline.} = s.curKey = k s.deserialize(v) -method deserializeFields*(o: RootRef, s: Deserializer) {.base.} = discard +proc deserialize*[T: not ContextFree](s: Deserializer, k: string, v: var T, gfx: RootRef) {.inline.} = + s.curKey = k + s.deserialize(v, gfx) + +method deserializeFields*(o: RootRef, s: Deserializer, gfx: RootRef) {.base.} = discard template typeOfSetElem[T](s: set[T]): typedesc = T -proc deserialize*[T](s: Deserializer, o: var T) = +proc deserialize*[T](s: Deserializer, o: var T, gfx: RootRef) = when o is object | tuple: s.beginObject() for k, v in fieldPairs(o): @@ -152,7 +160,7 @@ proc deserialize*[T](s: Deserializer, o: var T) = o.setLen(ln) for i in 0 ..< ln: s.curIndex = i - s.deserialize(o[i]) + s.deserialize(o[i], gfx) s.endObjectOrArray() elif o is ref object: # TODO: Implement shared references... @@ -161,7 +169,7 @@ proc deserialize*[T](s: Deserializer, o: var T) = var cn : string s.deserialize("_c", cn) o = T(newObjectOfClass(cn)) - o.deserializeFields(s) + o.deserializeFields(s, gfx) else: for k, v in fieldPairs(o[]): when compiles(s.deserialize(k, v)): @@ -353,5 +361,15 @@ when isMainModule: var s: JsonSerializer s.new() - s.serialize(o) + s.serialize(42) echo s.jsonNode() + # let d = newJsonDeserializer(s.jsonNode()) + # var deserialized: MyObj2 + # deserialize[MyObj2](d, deserialized, nil) + # echo repr o + + # var contextFree: int + # var ss = newJsonSerializer() + # let c = 42 + # serialize(ss, c) + # newJsonDeserializer(ss.jsonNode()).deserialize(contextFree) \ No newline at end of file diff --git a/nimx/slider.nim b/nimx/slider.nim index e5e5f1009..513a300d4 100644 --- a/nimx/slider.nim +++ b/nimx/slider.nim @@ -49,7 +49,7 @@ void compose() { """ method draw*(s: Slider, r: Rect) = - sliderComposition.draw s.bounds: + draw s.gfx, sliderComposition, s.bounds: setUniform("uPosition", s.mValue) proc `value=`*(s: Slider, p: Coord) = diff --git a/nimx/split_view.nim b/nimx/split_view.nim index cc171e0e7..fad2402ae 100644 --- a/nimx/split_view.nim +++ b/nimx/split_view.nim @@ -14,8 +14,8 @@ type SplitView* = ref object of View hoveredDivider: int initialDragPos: Point -method init*(v: SplitView, r: Rect) = - procCall v.View.init(r) +method init*(v: SplitView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.hoveredDivider = -1 v.mResizable = true v.trackMouseOver(true) diff --git a/nimx/stack_view.nim b/nimx/stack_view.nim index 11471edd8..b56972731 100644 --- a/nimx/stack_view.nim +++ b/nimx/stack_view.nim @@ -6,14 +6,14 @@ import nimx/color type StackView* = ref object of View -method init*(v: StackView, r: Rect) = - procCall v.View.init(r) +method init*(v: StackView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.backgroundColor = contentViewColor() proc recalculateContent(v: StackView) -proc newStackView*(r: Rect): StackView = +proc newStackView*(gfx: GraphicsContext, r: Rect): StackView = result.new() - result.init(r) + result.init(gfx, r) result.recalculateContent() method draw(v: StackView, r: Rect) = diff --git a/nimx/table_view.nim b/nimx/table_view.nim index 102db188f..c444482bb 100644 --- a/nimx/table_view.nim +++ b/nimx/table_view.nim @@ -43,14 +43,14 @@ proc `createCell=`*(v: TableView, p: proc(): TableViewCell) = proc `createCell=`*(v: TableView, p: proc(column: int): TableViewCell) = v.mCreateCell = p -proc newTableView*(r: Rect): TableView = # [Deprecated old layout] +proc newTableView*(gfx: GraphicsContext, r: Rect): TableView = # [Deprecated old layout] result.new() - result.init(r) + result.init(gfx, r) proc rebuildConstraints(v: TableView) -method init*(v: TableView, r: Rect) = - procCall v.View.init(r) +method init*(v: TableView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.numberOfColumns = 1 v.defaultRowHeight = 30 v.defaultColWidth = 50 @@ -176,7 +176,7 @@ proc configureRow(r: TableRow, top, height: Coord) {.inline.} = proc createRow(v: TableView): TableRow = let newLayout = v.usesNewLayout if newLayout: - result = TableRow.new(zeroRect) + result = TableRow.new(v.gfx, zeroRect) # result.addConstraint(result.layout.vars.height == height) result.addConstraint(result.layout.vars.leading == superPHS.leading) result.addConstraint(result.layout.vars.trailing == superPHS.trailing) @@ -195,7 +195,7 @@ proc createRow(v: TableView): TableRow = lastCell.addConstraint(lastCell.layout.vars.trailing == superPHS.trailing) else: - result = TableRow.new(newRect(0, 0, if v.numberOfColumns == 1: v.bounds.width else: (v.numberOfColumns.Coord * v.defaultColWidth).Coord, v.defaultRowHeight)) + result = TableRow.new(v.gfx, newRect(0, 0, if v.numberOfColumns == 1: v.bounds.width else: (v.numberOfColumns.Coord * v.defaultColWidth).Coord, v.defaultRowHeight)) result.setFrame(newRect(0, 0, v.bounds.width, 50)) var px = 0.0 @@ -311,12 +311,12 @@ method draw*(v: TableView, r: Rect) = procCall v.View.draw(r) if not v.usesNewLayout: var needsDisplay = false - if not v.window.isNil: + if not v.superview.isNil: needsDisplay = v.window.needsDisplay v.updateCellsInVisibleRect() - if not v.window.isNil: + if not v.superview.isNil: v.window.needsDisplay = needsDisplay proc isRowSelected*(t: TableView, row: int): bool = t.selectedRows.contains(row) diff --git a/nimx/table_view2.nim b/nimx/table_view2.nim index 0f3abb0c8..9d896d76b 100644 --- a/nimx/table_view2.nim +++ b/nimx/table_view2.nim @@ -43,8 +43,8 @@ proc `createCell=`*(v: TableView, p: proc(column: int): TableViewCell) = proc rebuildConstraints(v: TableView) -method init*(v: TableView, r: Rect) = - procCall v.View.init(r) +method init*(v: TableView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.numberOfColumns = 1 v.defaultRowHeight = 30 v.defaultColWidth = 50 @@ -132,7 +132,7 @@ proc visibleRect(v: View): Rect = # TODO: This can be more generic. Move to view result = v.bounds # method draw*(v: TableView, r: Rect) = -# let c = currentContext() +# template c: untyped = v.gfx # c.fillColor = blackColor() # let r = inset(visibleRect(v), 5, 5) # c.drawRect(r) diff --git a/nimx/table_view_cell.nim b/nimx/table_view_cell.nim index 677dfd421..0a205179a 100644 --- a/nimx/table_view_cell.nim +++ b/nimx/table_view_cell.nim @@ -7,19 +7,19 @@ type TableViewCell* = ref object of View row*, col*: int selected*: bool -proc newTableViewCell*(): TableViewCell = +proc newTableViewCell*(gfx: GraphicsContext): TableViewCell = result.new() - result.init(zeroRect) + result.init(gfx, zeroRect) -proc newTableViewCell*(r: Rect): TableViewCell = +proc newTableViewCell*(gfx: GraphicsContext, r: Rect): TableViewCell = result.new() - result.init(r) + result.init(gfx, r) -proc newTableViewCell*(s: Size): TableViewCell = - newTableViewCell(newRect(zeroPoint, s)) +proc newTableViewCell*(gfx: GraphicsContext, s: Size): TableViewCell = + newTableViewCell(gfx, newRect(zeroPoint, s)) -proc newTableViewCell*(v: View): TableViewCell = - result = newTableViewCell(v.frame.size) +proc newTableViewCell*(gfx: GraphicsContext, v: View): TableViewCell = + result = newTableViewCell(gfx, v.frame.size) v.autoresizingMask = { afFlexibleWidth, afFlexibleHeight } result.addSubview(v) @@ -30,11 +30,11 @@ proc enclosingTableViewCell*(v: View): TableViewCell {.inline.} = v.enclosingViewOfType(TableViewCell) method draw*(c: TableViewCell, r: Rect) = + template gfx: untyped = c.gfx if c.selected: - let ctx = currentContext() - ctx.fillColor = c.selectionColor() - ctx.strokeWidth = 0 - ctx.drawRect(c.bounds) + gfx.fillColor = c.selectionColor() + gfx.strokeWidth = 0 + gfx.drawRect(c.bounds) procCall c.View.draw(r) registerClass(TableViewCell) diff --git a/nimx/text_field.nim b/nimx/text_field.nim index b08028e3a..994250b4f 100644 --- a/nimx/text_field.nim +++ b/nimx/text_field.nim @@ -3,7 +3,7 @@ import context import font import types import event -import abstract_window +import window import unistring import unicode import timer @@ -38,7 +38,7 @@ type template len[T](s: Slice[T]): T = s.b - s.a -var cursorPos = 0 +var cursorPos = 0 # TODO: globals var cursorVisible = true var cursorUpdateTimer : Timer @@ -82,39 +82,46 @@ proc `formattedText=`*(tf: TextField, t: FormattedText) = template formattedText*(tf: TextField): FormattedText = tf.mText -proc newTextField*(r: Rect): TextField = +proc newTextField*(gfx: GraphicsContext, r: Rect): TextField {.deprecated.} = result.new() - result.init(r) + result.init(gfx, r) -proc newTextField*(parent: View = nil, position: Point = newPoint(0, 0), size: Size = newSize(100, 20), text: string = ""): TextField = - result = newTextField(newRect(position.x, position.y, size.width, size.height)) +proc newTextField*(parent: View = nil, gfx: GraphicsContext, position: Point = newPoint(0, 0), size: Size = newSize(100, 20), text: string = ""): TextField = + result = newTextField(gfx, newRect(position.x, position.y, size.width, size.height)) result.editable = true result.selectable = true result.mText.text = text if not isNil(parent): parent.addSubview(result) -proc newLabel*(r: Rect): TextField = - result = newTextField(r) +proc newLabel*(gfx: GraphicsContext, r: Rect): TextField {.deprecated.} = + result = newTextField(gfx, r) result.editable = false result.selectable = false result.backgroundColor.a = 0 -proc newLabel*(parent: View = nil, position: Point = newPoint(0, 0), size: Size = newSize(100, 20), text: string = "label"): TextField = - result = newLabel(newRect(position.x, position.y, size.width, size.height)) +proc newLabel*(parent: View = nil, gfx: GraphicsContext, position: Point = newPoint(0, 0), size: Size = newSize(100, 20), text: string = "label"): TextField = + result = newLabel(gfx, newRect(position.x, position.y, size.width, size.height)) result.editable = false result.selectable = false result.mText.text = text if not isNil(parent): parent.addSubview(result) -proc `textColor=`*(t: TextField, c: Color)= - t.mText.setTextColorInRange(0, -1, c) +proc `textColor=`*(t: TextField, c: Color) = + template gfx: untyped = t.gfx + template fontCtx: untyped = gfx.fontCtx + template gl: untyped = gfx.gl + setTextColorInRange(fontCtx, gl, t.mText, 0, -1, c) -proc textColor*(t: TextField): Color = t.mText.colorOfRuneAtPos(0).color1 +proc textColor*(t: TextField): Color = + template gfx: untyped = t.gfx + template fontCtx: untyped = gfx.fontCtx + template gl: untyped = gfx.gl + colorOfRuneAtPos(fontCtx, gl, t.mText, 0).color1 -method init*(t: TextField, r: Rect) = - procCall t.Control.init(r) +method init*(t: TextField, gfx: GraphicsContext, r: Rect) = + procCall t.Control.init(gfx, r) t.editable = true t.selectable = true t.textSelection = -1 .. -1 @@ -123,36 +130,43 @@ method init*(t: TextField, r: Rect) = t.mText = newFormattedText() t.mText.verticalAlignment = vaCenter -method init*(v: Label, r: Rect) = - procCall v.TextField.init(r) +method init*(v: Label, gfx: GraphicsContext, r: Rect) = + procCall v.TextField.init(gfx, r) v.editable = false v.selectable = false proc `font=`*(t: TextField, f: Font) = + template gfx: untyped = t.gfx + template fontCtx: untyped = gfx.fontCtx + template gl: untyped = gfx.gl t.mFont = f - t.mText.setFontInRange(0, -1, t.mFont) + setFontInRange(fontCtx, gl, t.mText, 0, -1, t.mFont) proc font*(t: TextField): Font = + template gfx: untyped = t.gfx + template fontCtx: untyped = gfx.fontCtx if t.mFont.isNil: - result = systemFont() + result = systemFont(fontCtx) else: result = t.mFont proc isEditing*(t: TextField): bool = t.editable and t.isFirstResponder -proc drawCursorWithRect(r: Rect) = +proc drawCursorWithRect(c: GraphicsContext, r: Rect) = if cursorVisible: - let c = currentContext() c.fillColor = newGrayColor(0.28) c.strokeWidth = 0 c.drawRect(r) proc cursorRect(t: TextField): Rect = - let ln = t.mText.lineOfRuneAtPos(cursorPos) - let y = t.mText.lineTop(ln) + t.mText.topOffset() - let fh = t.mText.lineHeight(ln) - let lineX = t.mText.lineLeft(ln) + template gfx: untyped = t.gfx + template fontCtx: untyped = gfx.fontCtx + template gl: untyped = gfx.gl + let ln = lineOfRuneAtPos(fontCtx, gl, t.mText, cursorPos) + let y = lineTop(fontCtx, gl, t.mText, ln) + topOffset(fontCtx, gl, t.mText) + let fh = lineHeight(fontCtx, gl, t.mText, ln) + let lineX = lineLeft(fontCtx, gl, t.mText, ln) newRect(leftMargin + cursorOffset + lineX, y, 2, fh) proc bumpCursorVisibility(t: TextField) = @@ -201,7 +215,7 @@ proc selectInRange*(t: TextField, a, b: int) = t.textSelection.b = bb proc selectAll*(t: TextField) = - t.selectInRange(0, t.mText.text.len) + selectInRange(t, 0, t.mText.text.len) t.setNeedsDisplay() proc selectionRange(t: TextField): Slice[int] = @@ -209,42 +223,44 @@ proc selectionRange(t: TextField): Slice[int] = if result.a > result.b: swap(result.a, result.b) proc selectedText*(t: TextField): string = - let s = t.selectionRange() + let s = selectionRange(t) if s.len > 0: if not t.mText.isNil: - result = t.mText.text.runeSubStr(s.a, s.b - s.a) + result = runeSubStr(t.mText.text, s.a, s.b - s.a) proc drawSelection(t: TextField) {.inline.} = - let c = currentContext() - c.fillColor = newColor(0.0, 0.0, 1.0, 0.5) - let startLine = t.mText.lineOfRuneAtPos(t.textSelection.a) - let endLine = t.mText.lineOfRuneAtPos(t.textSelection.b) - let startOff = t.mText.xOfRuneAtPos(t.textSelection.a) - let endOff = t.mText.xOfRuneAtPos(t.textSelection.b) - let top = t.mText.topOffset() + template gfx: untyped = t.gfx + template fontCtx: untyped = t.gfx.fontCtx + template gl: untyped = t.gfx.gl + gfx.fillColor = newColor(0.0, 0.0, 1.0, 0.5) + let startLine = lineOfRuneAtPos(fontCtx, gl, t.mText, t.textSelection.a) + let endLine = lineOfRuneAtPos(fontCtx, gl, t.mText, t.textSelection.b) + let startOff = xOfRuneAtPos(fontCtx, gl, t.mText, t.textSelection.a) + let endOff = xOfRuneAtPos(fontCtx, gl, t.mText, t.textSelection.b) + let top = topOffset(fontCtx, gl, t.mText) var r: Rect - r.origin.y = t.mText.lineTop(startLine) + top - r.size.height = t.mText.lineHeight(startLine) - let lineX = t.mText.lineLeft(startLine) + r.origin.y = lineTop(fontCtx, gl, t.mText, startLine) + top + r.size.height = lineHeight(fontCtx, gl, t.mText, startLine) + let lineX = lineLeft(fontCtx, gl, t.mText, startLine) r.origin.x = leftMargin + startOff + lineX if endLine == startLine: r.size.width = endOff - startOff else: - r.size.width = t.mText.lineWidth(startLine) - startOff - c.drawRect(r) + r.size.width = lineWidth(fontCtx, gl, t.mText, startLine) - startOff + gfx.drawRect(r) for i in startLine + 1 ..< endLine: - r.origin.y = t.mText.lineTop(i) + top - r.size.height = t.mText.lineHeight(i) - r.origin.x = leftMargin + t.mText.lineLeft(i) - r.size.width = t.mText.lineWidth(i) + r.origin.y = lineTop(fontCtx, gl, t.mText, i) + top + r.size.height = lineHeight(fontCtx, gl, t.mText, i) + r.origin.x = leftMargin + lineLeft(fontCtx, gl, t.mText, i) + r.size.width = lineWidth(fontCtx, gl, t.mText, i) if r.size.width < 5: r.size.width = 5 - c.drawRect(r) + gfx.drawRect(r) if startLine != endLine: - r.origin.y = t.mText.lineTop(endLine) + top - r.size.height = t.mText.lineHeight(endLine) - r.origin.x = leftMargin + t.mText.lineLeft(endLine) + r.origin.y = lineTop(fontCtx, gl, t.mText, endLine) + top + r.size.height = lineHeight(fontCtx, gl, t.mText, endLine) + r.origin.x = leftMargin + lineLeft(fontCtx, gl, t.mText, endLine) r.size.width = endOff - c.drawRect(r) + gfx.drawRect(r) #todo: replace by generic visibleRect which should be implemented in future proc visibleRect(t: TextField): Rect = @@ -258,12 +274,13 @@ proc visibleRect(t: TextField): Rect = method draw*(t: TextField, r: Rect) = procCall t.View.draw(r) - let c = currentContext() + template gfx: untyped = t.gfx + if t.editable and t.hasBezel: - c.fillColor = t.backgroundColor - c.strokeColor = newGrayColor(0.74) - c.strokeWidth = 1.0 - c.drawRect(t.bounds) + gfx.fillColor = t.backgroundColor + gfx.strokeColor = newGrayColor(0.74) + gfx.strokeWidth = 1.0 + gfx.drawRect(t.bounds) t.mText.boundingSize = t.bounds.size @@ -278,19 +295,21 @@ method draw*(t: TextField, r: Rect) = t.mText.overrideColor.a = 0 if t.bounds.height > t.window.bounds.height: - c.drawText(pt, t.mText, t.visibleRect()) + gfx.drawText(pt, t.mText, t.visibleRect()) else: - c.drawText(pt, t.mText) + gfx.drawText(pt, t.mText) if t.isEditing: if t.hasBezel: t.drawFocusRing() - drawCursorWithRect(t.cursorRect()) + drawCursorWithRect(gfx, t.cursorRect()) method acceptsFirstResponder*(t: TextField): bool = t.editable method onTouchEv*(t: TextField, e: var Event): bool = result = false + template fontCtx: untyped = t.gfx.fontCtx + template gl: untyped = t.gfx.gl var pt = e.localPosition case e.buttonState of bsDown: @@ -305,7 +324,7 @@ method onTouchEv*(t: TextField, e: var Event): bool = cursorPos = 0 cursorOffset = 0 else: - t.mText.getClosestCursorPositionToPoint(pt, cursorPos, cursorOffset) + getClosestCursorPositionToPoint(fontCtx, gl, t.mText, pt, cursorPos, cursorOffset) t.textSelection = cursorPos .. cursorPos t.bumpCursorVisibility() @@ -315,7 +334,7 @@ method onTouchEv*(t: TextField, e: var Event): bool = t.window.startTextInput(t.convertRectToWindow(t.bounds)) if t.textSelection.len != 0: let oldPos = cursorPos - t.mText.getClosestCursorPositionToPoint(pt, cursorPos, cursorOffset) + getClosestCursorPositionToPoint(fontCtx, gl, t.mText, pt, cursorPos, cursorOffset) t.updateSelectionWithCursorPos(oldPos, cursorPos) if t.textSelection.len == 0: t.textSelection = -1 .. -1 @@ -327,14 +346,16 @@ method onTouchEv*(t: TextField, e: var Event): bool = of bsUnknown: if t.selectable: let oldPos = cursorPos - t.mText.getClosestCursorPositionToPoint(pt, cursorPos, cursorOffset) + getClosestCursorPositionToPoint(fontCtx, gl, t.mText, pt, cursorPos, cursorOffset) t.updateSelectionWithCursorPos(oldPos, cursorPos) t.setNeedsDisplay() result = false proc updateCursorOffset(t: TextField) = - cursorOffset = t.mText.xOfRuneAtPos(cursorPos) + template fontCtx: untyped = t.gfx.fontCtx + template gl: untyped = t.gfx.gl + cursorOffset = xOfRuneAtPos(fontCtx, gl, t.mText, cursorPos) proc `cursorPosition=`*(t: TextField, pos: int) = cursorPos = pos @@ -342,26 +363,30 @@ proc `cursorPosition=`*(t: TextField, pos: int) = t.bumpCursorVisibility() proc clearSelection(t: TextField) = + template gfx: untyped = t.gfx + template fontCtx: untyped = t.gfx.fontCtx + template gl: untyped = t.gfx.gl # Clears selected text let s = t.selectionRange() - t.mText.uniDelete(s.a, s.b - 1) + uniDelete(fontCtx, gl, t.mText, s.a, s.b - 1) cursorPos = s.a t.updateCursorOffset() t.textSelection = -1 .. -1 proc insertText(t: TextField, s: string) = - #if t.mText.isNil: t.mText.text = "" + template fontCtx: untyped = t.gfx.fontCtx + template gl: untyped = t.gfx.gl - let th = t.mText.totalHeight + let th = totalHeight(fontCtx, gl, t.mText) if t.textSelection.len > 0: t.clearSelection() - t.mText.uniInsert(cursorPos, s) + uniInsert(fontCtx, gl, t.mText, cursorPos, s) cursorPos += s.runeLen t.updateCursorOffset() t.bumpCursorVisibility() - let newTh = t.mText.totalHeight + let newTh = totalHeight(fontCtx, gl, t.mText) if th != newTh: var s = t.bounds.size s.height = newTh @@ -371,6 +396,8 @@ proc insertText(t: TextField, s: string) = t.sendAction() method onKeyDown*(t: TextField, e: var Event): bool = + template fontCtx: untyped = t.gfx.fontCtx + template gl: untyped = t.gfx.gl if e.keyCode == VirtualKey.Tab: return false @@ -378,7 +405,7 @@ method onKeyDown*(t: TextField, e: var Event): bool = if e.keyCode == VirtualKey.Backspace: if t.textSelection.len > 0: t.clearSelection() elif cursorPos > 0: - t.mText.uniDelete(cursorPos - 1, cursorPos - 1) + uniDelete(fontCtx, gl, t.mText, cursorPos - 1, cursorPos - 1) dec cursorPos if t.continuous: t.sendAction() @@ -389,7 +416,7 @@ method onKeyDown*(t: TextField, e: var Event): bool = elif e.keyCode == VirtualKey.Delete and not t.mText.isNil: if t.textSelection.len > 0: t.clearSelection() elif cursorPos < t.mText.runeLen: - t.mText.uniDelete(cursorPos, cursorPos) + uniDelete(fontCtx, gl, t.mText, cursorPos, cursorPos) if t.continuous: t.sendAction() t.bumpCursorVisibility() @@ -449,9 +476,9 @@ method onKeyDown*(t: TextField, e: var Event): bool = elif t.multiline: if e.keyCode == VirtualKey.Down: let oldCursorPos = cursorPos - let ln = t.mText.lineOfRuneAtPos(cursorPos) + let ln = lineOfRuneAtPos(fontCtx, gl, t.mText, cursorPos) var offset: Coord - t.mText.getClosestCursorPositionToPointInLine(ln + 1, newPoint(cursorOffset, 0), cursorPos, offset) + getClosestCursorPositionToPointInLine(fontCtx, gl, t.mText, ln + 1, newPoint(cursorOffset, 0), cursorPos, offset) cursorOffset = offset if e.modifiers.anyShift(): t.updateSelectionWithCursorPos(oldCursorPos, cursorPos) @@ -461,10 +488,10 @@ method onKeyDown*(t: TextField, e: var Event): bool = result = true elif e.keyCode == VirtualKey.Up: let oldCursorPos = cursorPos - let ln = t.mText.lineOfRuneAtPos(cursorPos) + let ln = lineOfRuneAtPos(fontCtx, gl, t.mText, cursorPos) if ln > 0: var offset: Coord - t.mText.getClosestCursorPositionToPointInLine(ln - 1, newPoint(cursorOffset, 0), cursorPos, offset) + getClosestCursorPositionToPointInLine(fontCtx, gl, t.mText, ln - 1, newPoint(cursorOffset, 0), cursorPos, offset) cursorOffset = offset if e.modifiers.anyShift(): t.updateSelectionWithCursorPos(oldCursorPos, cursorPos) diff --git a/nimx/toolbar.nim b/nimx/toolbar.nim index 4ffa52b1f..979618c88 100644 --- a/nimx/toolbar.nim +++ b/nimx/toolbar.nim @@ -5,8 +5,8 @@ import nimx/linear_layout type Toolbar* = ref object of LinearLayout -method init*(v: Toolbar, r: Rect) = - procCall v.LinearLayout.init(r) +method init*(v: Toolbar, gfx: GraphicsContext, r: Rect) = + procCall v.LinearLayout.init(gfx, r) v.horizontal = true v.leftMargin = 10 v.padding = 3 @@ -16,7 +16,7 @@ method init*(v: Toolbar, r: Rect) = v.enableDraggingByBackground() method draw*(view: Toolbar, rect: Rect) = - let c = currentContext() + template c: untyped = view.gfx c.strokeWidth = 2 c.strokeColor = newGrayColor(0.6, 0.7) c.fillColor = newGrayColor(0.3, 0.7) diff --git a/nimx/ui_resource.nim b/nimx/ui_resource.nim index 9a1e5ca8c..7c36fd730 100644 --- a/nimx/ui_resource.nim +++ b/nimx/ui_resource.nim @@ -21,41 +21,45 @@ type proc `@`(str: string): UIResID = UIResID(hash(str)) -proc deserializeView*(jn: JsonNode): View = newJsonDeserializer(jn).deserialize(result) -proc deserializeView*(data: string): View = deserializeView(parseJson(data)) +proc deserializeView*(jn: JsonNode, gfx: GraphicsContext): View = + newJsonDeserializer(jn).deserialize(result, gfx) -proc loadAUX[T](path: string, deser: proc(j: JsonNode): T, onLoad: proc(v: T))= +proc deserializeView*(data: string, gfx: GraphicsContext): View = + deserializeView(parseJson(data), gfx) + +proc loadAUX[T](path: string, deser: proc(j: JsonNode, gfx: GraphicsContext): T, gfx: GraphicsContext, onLoad: proc(v: T))= loadAsset[JsonNode]("res://" & path) do(jn: JsonNode, err: string): - onLoad(deser(jn)) + onLoad(deser(jn, gfx)) -proc loadAUXAsync[T](path: string, deser: proc(j: JsonNode): T): Future[T] = +proc loadAUXAsync[T](path: string, deser: proc(j: JsonNode, gfx: GraphicsContext): T, gfx: GraphicsContext): Future[T] = when defined js: newPromise() do (resolve: proc(response: T)): - loadAUX[T](path, deser) do(v: T): + loadAUX[T](path, deser, gfx) do(v: T): resolve(v) else: let resf = newFuture[T]() - loadAUX[T](path, deser) do(v: T): + loadAUX[T](path, deser, gfx) do(v: T): resf.complete(v) return resf -proc loadView*(path: string, onLoad: proc(v: View))= - loadAUX[View](path, deserializeView, onLoad) +proc loadView*(path: string, gfx: GraphicsContext, onLoad: proc(v: View)) = + loadAUX[View](path, deserializeView, gfx, onLoad) -proc loadViewAsync*(path: string): Future[View] = - result = loadAUXAsync[View](path, deserializeView) +proc loadViewAsync*(path: string, gfx: GraphicsContext): Future[View] = + result = loadAUXAsync[View](path, deserializeView, gfx) -method deserializeFields*(v: View, s: Deserializer) = +method deserializeFields*(v: View, s: Deserializer, gfx: RootRef) = + assert not gfx.isNil var fr: Rect s.deserialize("frame", fr) - v.init(fr) + v.init(GraphicsContext(gfx), fr) var bounds:Rect s.deserialize("bounds", bounds) v.setBounds(bounds) var subviews: seq[View] - s.deserialize("subviews", subviews) + s.deserialize("subviews", subviews, GraphicsContext(gfx)) for sv in subviews: doAssert(not sv.isNil) v.addSubview(sv) @@ -77,20 +81,21 @@ proc newJUIResourceDeserializer*(n: JsonNode): UIResourceDeserializer = result.new() result.init(n) -proc deserializeUIResource*(jn: JsonNode): UIResource = +proc deserializeUIResource*(jn: JsonNode, gfx: GraphicsContext): UIResource = result.new() let deser = newJUIResourceDeserializer(jn) - deser.deserialize(result.mView) + deser.deserialize(result.mView, gfx) result.outlets = deser.deserTable result.actions = initTable[UIResID, UIActionCallback]() -proc deserializeUIResource*(data: string): UIResource = deserializeUIResource(parseJson(data)) +proc deserializeUIResource*(data: string, gfx: GraphicsContext): UIResource = + deserializeUIResource(parseJson(data), gfx) -proc loadUiResource*(path: string, onLoad: proc(v: UIResource)) = - loadAUX[UIResource](path, deserializeUIResource, onLoad) +proc loadUiResource*(path: string, gfx: GraphicsContext, onLoad: proc(v: UIResource)) = + loadAUX[UIResource](path, deserializeUIResource, gfx, onLoad) -proc loadUiResourceAsync*(path: string): Future[UIResource] = - result = loadAUXAsync[UIResource](path, deserializeUIResource) +proc loadUiResourceAsync*(path: string, gfx: GraphicsContext): Future[UIResource] = + result = loadAUXAsync[UIResource](path, deserializeUIResource, gfx) proc getView(ui: UIResource, T: typedesc, id: UIResID): T = result = ui.outlets.getOrDefault(id).T diff --git a/nimx/view.nim b/nimx/view.nim index 9c6e89eef..4835a4653 100644 --- a/nimx/view.nim +++ b/nimx/view.nim @@ -6,28 +6,12 @@ import serializers import kiwi import notification_center -export types +export types, context export animation_runner, class_registry const NimxFristResponderChangedInWindow* = "NimxFristResponderChangedInWindow" -type AutoresizingFlag* = enum - afFlexibleMinX - afFlexibleMaxX - afFlexibleMinY - afFlexibleMaxY - afFlexibleWidth - afFlexibleHeight - -type ClipType* = enum - ctNone - ctDefaultClip - type - GestureDetector* = ref object of RootObj - - DragDestinationDelegate* = ref object of RootObj - ConstraintWithPrototype = object proto: Constraint inst: Constraint @@ -35,6 +19,22 @@ type LayoutInfo = object vars*: LayoutVars constraints: seq[ConstraintWithPrototype] + + ClipType* = enum + ctNone + ctDefaultClip + + AutoresizingFlag* = enum + afFlexibleMinX + afFlexibleMaxX + afFlexibleMinY + afFlexibleMaxY + afFlexibleWidth + afFlexibleHeight + + GestureDetector* = ref object of RootObj + + DragDestinationDelegate* = ref object of RootObj View* = ref object of RootRef window*: Window @@ -54,6 +54,7 @@ type usesNewLayout*: bool dragDestination*: DragDestinationDelegate layout*: LayoutInfo + gfx*: GraphicsContext Window* = ref object of View firstResponder*: View ## handler of untargeted (keyboard and menu) input @@ -165,7 +166,9 @@ proc constraints*(v: View): seq[Constraint] = result = newSeqOfCap[Constraint](v.layout.constraints.len) for c in v.layout.constraints: result.add(c.proto) -method init*(v: View, frame: Rect) {.base.} = +method init*(v: View, gfx: GraphicsContext, frame: Rect) {.base.} = + assert not gfx.isNil + v.gfx = gfx v.frame = frame v.layout.init() v.bounds = newRect(0, 0, frame.width, frame.height) @@ -204,13 +207,13 @@ proc removeGestureDetector*(v: View, d: GestureDetector) = proc removeAllGestureDetectors*(v: View) = v.gestureDetectors.setLen(0) -proc new*[V](v: typedesc[V], frame: Rect): V = # Deprecated +proc new*[V](v: typedesc[V], gfx: GraphicsContext, frame: Rect): V {.deprecated.} = result.new() - result.init(frame) + result.init(gfx, frame) -proc newView*(frame: Rect): View = # Deprecated +proc newView*(gfx: GraphicsContext, frame: Rect): View {.deprecated.} = result.new() - result.init(frame) + result.init(gfx, frame) method convertPointToParent*(v: View, p: Point): Point {.base.} = p + v.frame.origin - v.bounds.origin method convertPointFromParent*(v: View, p: Point): Point {.base.} = p - v.frame.origin + v.bounds.origin @@ -263,7 +266,7 @@ template isFirstResponder*(v: View): bool = #### method viewWillMoveToSuperview*(v: View, s: View) {.base.} = discard method viewWillMoveToWindow*(v: View, w: Window) {.base.} = - if not v.window.isNil: + if not v.window.isNil and not v.superview.isNil: v.window.removeMouseOverListener(v) if v.window.firstResponder == v and w != v.window: discard v.window.makeFirstResponder(nil) @@ -280,8 +283,8 @@ method viewWillMoveToWindow*(v: View, w: Window) {.base.} = s.window = v.window s.viewWillMoveToWindow(w) -method viewDidMoveToWindow*(v: View){.base.} = - if not v.window.isNil: +method viewDidMoveToWindow*(v: View) {.base.} = + if not v.window.isNil and not v.superview.isNil: for c in v.layout.constraints.mitems: v.instantiateConstraint(c) @@ -392,7 +395,7 @@ proc drawWithinSuperview*(v: View) = # Assume current coordinate system is superview if v.hidden: return - let c = currentContext() + template c: untyped = v.gfx var tmpTransform = c.transform if v.bounds.size == v.frame.size: # Common case: bounds scale is 1.0 @@ -411,7 +414,7 @@ proc drawWithinSuperview*(v: View) = v.recursiveDrawSubviews() method draw*(view: View, rect: Rect) {.base.} = - let c = currentContext() + template c: untyped = view.gfx if view.backgroundColor.a > 0.001: c.fillColor = view.backgroundColor c.strokeWidth = 0 @@ -428,15 +431,15 @@ proc recursiveDrawSubviews*(view: View) = view.drawSubviews() proc drawFocusRing*(v: View) = - let c = currentContext() + template c: untyped = v.gfx c.fillColor = clearColor() c.strokeColor = newColor(0.59, 0.76, 0.95, 0.9) c.strokeWidth = 3 c.drawRoundedRect(v.bounds.inset(-1, -1), 2) -method setFrame*(v: View, r: Rect) {.base.} # Deprecated +method setFrame*(v: View, r: Rect) {.base, deprecated.} -method resizeSubviews*(v: View, oldSize: Size) {.base.} = # Deprecated +method resizeSubviews*(v: View, oldSize: Size) {.base, deprecated.} = let sizeDiff = v.frame.size - oldSize for s in v.subviews: @@ -466,29 +469,29 @@ proc recursiveUpdateLayout*(v: View, relPoint: Point) = for s in v.subviews: s.recursiveUpdateLayout(relPoint) -method setBoundsSize*(v: View, s: Size) {.base.} = # Deprecated +method setBoundsSize*(v: View, s: Size) {.base, deprecated.} = let oldSize = v.bounds.size v.bounds.size = s v.setNeedsDisplay() v.resizeSubviews(oldSize) -method setBoundsOrigin*(v: View, o: Point) {.base.} = # Deprecated +method setBoundsOrigin*(v: View, o: Point) {.base, deprecated.} = v.bounds.origin = o v.setNeedsDisplay() -proc setBounds*(v: View, b: Rect) = # Deprecated +proc setBounds*(v: View, b: Rect) {.deprecated.} = v.setBoundsOrigin(b.origin) v.setBoundsSize(b.size) -method setFrameSize*(v: View, s: Size) {.base.} = # Deprecated +method setFrameSize*(v: View, s: Size) {.base, deprecated.} = v.frame.size = s v.setBoundsSize(s) -method setFrameOrigin*(v: View, o: Point) {.base.} = # Deprecated +method setFrameOrigin*(v: View, o: Point) {.base, deprecated.} = v.frame.origin = o v.setNeedsDisplay() -method setFrame*(v: View, r: Rect) = # Deprecated +method setFrame*(v: View, r: Rect) {.deprecated.} = if v.frame.origin != r.origin: v.setFrameOrigin(r.origin) if v.frame.size != r.size: @@ -497,9 +500,9 @@ method setFrame*(v: View, r: Rect) = # Deprecated method frame*(v: View): Rect {.base.} = v.frame method bounds*(v: View): Rect {.base.} = v.bounds -method subviewDidChangeDesiredSize*(v: View, sub: View, desiredSize: Size) {.base.} = discard # Deprecated +method subviewDidChangeDesiredSize*(v: View, sub: View, desiredSize: Size) {.base, deprecated.} = discard -proc autoresizingMaskFromStrLit(s: string): set[AutoresizingFlag] {.compileTime.} = # Deprecated +proc autoresizingMaskFromStrLit(s: string): set[AutoresizingFlag] {.compileTime, deprecated.} = case s[0] of 'w': result.incl(afFlexibleWidth) of 'l': result.incl(afFlexibleMinX) @@ -511,7 +514,7 @@ proc autoresizingMaskFromStrLit(s: string): set[AutoresizingFlag] {.compileTime. of 'b': result.incl(afFlexibleMaxY) else: assert(false, "Wrong autoresizing mask!") -template `resizingMask=`*(v: View, s: static[string]) = # Deprecated +template `resizingMask=`*(v: View, s: static[string]) {.deprecated.} = const m = autoresizingMaskFromStrLit(s) v.autoresizingMask = m diff --git a/nimx/view_dragging_listener.nim b/nimx/view_dragging_listener.nim index e288f2101..b33e935fd 100644 --- a/nimx/view_dragging_listener.nim +++ b/nimx/view_dragging_listener.nim @@ -33,7 +33,7 @@ method onScrollProgress(ls: ResizingScrollListener, dx, dy : float32, e : var Ev v.setFrameSize(ls.originalSize + newSize(dx, dy)) method draw(k: ResizingKnob, r: Rect) = - let c = currentContext() + template c: untyped = k.gfx c.strokeWidth = 2 c.strokeColor = newGrayColor(0.2, 0.7) let b = k.bounds @@ -45,7 +45,7 @@ method draw(k: ResizingKnob, r: Rect) = proc enableViewResizing*(v: View) = const size = 20 - let resizingKnob = ResizingKnob.new(newRect(v.bounds.width - size, v.bounds.height - size, size, size)) + let resizingKnob = ResizingKnob.new(v.gfx, newRect(v.bounds.width - size, v.bounds.height - size, size, size)) resizingKnob.autoresizingMask = {afFlexibleMinX, afFlexibleMinY} v.addSubview(resizingKnob) var listener: ResizingScrollListener diff --git a/nimx/view_render_to_image.nim b/nimx/view_render_to_image.nim index a0262aee6..ae2e9588c 100644 --- a/nimx/view_render_to_image.nim +++ b/nimx/view_render_to_image.nim @@ -1,7 +1,7 @@ import nimx / [ view, render_to_image, image, types, context ] proc renderToImage*(v: View, image: SelfContainedImage)= - image.draw: + draw v.gfx, image: v.recursiveDrawSubviews() proc screenShot*(v: View):SelfContainedImage= diff --git a/nimx/window_view.nim b/nimx/window_view.nim new file mode 100644 index 000000000..f2f60f707 --- /dev/null +++ b/nimx/window_view.nim @@ -0,0 +1,17 @@ +import view + +type + Window* = ref object of View + gfx*: GraphicsContext + firstResponder*: View ## handler of untargeted (keyboard and menu) input + animationRunners*: seq[AnimationRunner] + needsDisplay*: bool + needsLayout*: bool + mouseOverListeners*: seq[View] + pixelRatio*: float32 + viewportPixelRatio*: float32 + mActiveBgColor*: Color + layoutSolver*: Solver + onClose*: proc() + mCurrentTouches*: TableRef[int, View] + mAnimationEnabled*: bool diff --git a/opengl b/opengl new file mode 160000 index 000000000..e53096f4e --- /dev/null +++ b/opengl @@ -0,0 +1 @@ +Subproject commit e53096f4e7f581b5c90c1912441f3059be97e0d9 diff --git a/os_files b/os_files new file mode 160000 index 000000000..5c154b9f9 --- /dev/null +++ b/os_files @@ -0,0 +1 @@ +Subproject commit 5c154b9f9b81d047938117c00c2579c0c3754d8b diff --git a/rod.git b/rod.git new file mode 160000 index 000000000..198d92a8a --- /dev/null +++ b/rod.git @@ -0,0 +1 @@ +Subproject commit 198d92a8aac9f61fd69896262f93011b9fc148ed diff --git a/test/main.nim b/test/main.nim index c76e9c5c7..a30fd6f06 100755 --- a/test/main.nim +++ b/test/main.nim @@ -1,7 +1,7 @@ #!/usr/local/bin/nim c -r --threads:on import sample_registry -import nimx / [ view, scroll_view, table_view, text_field, autotest, window, linear_layout ] +import nimx / [ view, scroll_view, table_view, text_field, autotest, window, linear_layout, split_view, layout ] import sequtils, intsets {.warning[UnusedImport]: off.} @@ -31,24 +31,45 @@ proc startApplication() = else: newWindow(newRect(40, 40, 800, 600)) - mainWindow.title = "NimX Sample" - - var currentView = View.new(newRect(0, 0, mainWindow.bounds.width - 100, mainWindow.bounds.height)) - - let splitView = newHorizontalLayout(mainWindow.bounds) + # mainWindow.makeLayout: + # title: "NimX Sample" + # - SplitView: + # - ScrollView: + # - TableView: + # width == 120 + # height == super + # numberOfRows do() -> int: + # 0 + # createCell do() -> TableViewCell: + # result = TableViewCell.new(zeroRect) + # result.makeLayout: + # top == super + # bottom == super + # width == super + # - Label: + # frame == super + # width == super + # configureCell do(c: TableViewCell): discard + # - View as currentView: + # width == mainWindow.bounds.width - 100 + # height == mainWindow.bounds.height + + var currentView = View.new(mainWindow.gfx, newRect(0, 0, mainWindow.bounds.width - 100, mainWindow.bounds.height)) + + let splitView = newHorizontalLayout(mainWindow.gfx, mainWindow.bounds) splitView.resizingMask = "wh" splitView.userResizeable = true mainWindow.addSubview(splitView) - let tableView = newTableView(newRect(0, 0, 120, mainWindow.bounds.height)) + let tableView = newTableView(mainWindow.gfx, newRect(0, 0, 120, mainWindow.bounds.height)) tableView.resizingMask = "rh" - splitView.addSubview(newScrollView(tableView)) + splitView.addSubview(newScrollView(mainWindow.gfx, tableView)) splitView.addSubview(currentView) splitView.setDividerPosition(120, 0) tableView.numberOfRows = proc: int = allSamples.len tableView.createCell = proc (): TableViewCell = - result = newTableViewCell(newLabel(newRect(0, 0, 120, 20))) + result = newTableViewCell(mainWindow.gfx, newLabel(mainWindow.gfx, newRect(0, 0, 120, 20))) tableView.configureCell = proc (c: TableViewCell) = TextField(c.subviews[0]).text = allSamples[c.row].name tableView.onSelectionChange = proc() = @@ -56,7 +77,7 @@ proc startApplication() = if selectedRows.len > 0: let firstSelectedRow = selectedRows[0] let nv = View(newObjectOfClass(allSamples[firstSelectedRow].className)) - nv.init(currentView.frame) + nv.init(mainWindow.gfx, currentView.frame) nv.resizingMask = "wh" splitView.replaceSubview(currentView, nv) currentView = nv diff --git a/test/sample01_welcome.nim b/test/sample01_welcome.nim index af0204957..acd61862a 100644 --- a/test/sample01_welcome.nim +++ b/test/sample01_welcome.nim @@ -14,10 +14,10 @@ method onScroll*(v: CustomControl, e: var Event): bool = echo "custom scroll ", e.offset result = true -method init(v: WelcomeView, r: Rect) = - procCall v.View.init(r) - let autoTestButton = newButton(newRect(20, 20, 150, 20)) - let secondTestButton = newButton(newRect(20, 50, 150, 20)) +method init(v: WelcomeView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) + let autoTestButton = newButton(gfx, newRect(20, 20, 150, 20)) + let secondTestButton = newButton(gfx, newRect(20, 50, 150, 20)) autoTestButton.title = "Start Auto Tests" secondTestButton.title = "Second button" let tapd = newTapGestureDetector do(tapPoint : Point): @@ -36,7 +36,7 @@ method init(v: WelcomeView, r: Rect) = v.addGestureDetector(vtapd) var cc: CustomControl cc.new - cc.init(newRect(20, 80, 150, 20)) + cc.init(gfx, newRect(20, 80, 150, 20)) cc.clickable = true cc.backgroundColor = newColor(1.0,0.0,0.0,1.0) cc.onAction do(): @@ -68,12 +68,14 @@ void compose() { """ method draw(v: WelcomeView, r: Rect) = - let c = currentContext() + template gfx: untyped = v.gfx + template fontCtx: untyped = v.gfx.fontCtx + template gl: untyped = v.gfx.gl if v.welcomeFont.isNil: - v.welcomeFont = systemFontOfSize(64) - gradientComposition.draw(v.bounds) - let s = v.welcomeFont.sizeOfString(welcomeMessage) - c.fillColor = whiteColor() - c.drawText(v.welcomeFont, s.centerInRect(v.bounds), welcomeMessage) + v.welcomeFont = systemFontOfSize(fontCtx, 64) + draw(gfx, gradientComposition, v.bounds) + let s = sizeOfString(fontCtx, gl, v.welcomeFont, welcomeMessage) + gfx.fillColor = whiteColor() + gfx.drawText(v.welcomeFont, s.centerInRect(v.bounds), welcomeMessage) registerSample(WelcomeView, "Welcome") diff --git a/test/sample02_controls.nim b/test/sample02_controls.nim index ead505bc4..d7ce1d41e 100644 --- a/test/sample02_controls.nim +++ b/test/sample02_controls.nim @@ -6,23 +6,23 @@ import nimx/assets/asset_manager type ControlsSampleView = ref object of View -method init(v: ControlsSampleView, r: Rect) = - procCall v.View.init(r) +method init(v: ControlsSampleView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) - let label = newLabel(newRect(10, 10, 100, 20)) - let textField = newTextField(newRect(120, 10, v.bounds.width - 130, 20)) + let label = newLabel(gfx, newRect(10, 10, 100, 20)) + let textField = newTextField(gfx, newRect(120, 10, v.bounds.width - 130, 20)) textField.autoresizingMask = { afFlexibleWidth, afFlexibleMaxY } label.text = "Text field:" v.addSubview(label) v.addSubview(textField) - let button = newButton(newRect(10, 40, 100, 22)) + let button = newButton(gfx, newRect(10, 40, 100, 22)) button.title = "Button" button.onAction do(): textField.text = "Click! " v.addSubview(button) - let sc = SegmentedControl.new(newRect(120, 40, v.bounds.width - 130, 22)) + let sc = SegmentedControl.new(gfx, newRect(120, 40, v.bounds.width - 130, 22)) sc.segments = @["This", "is", "a", "segmented", "control"] sc.autoresizingMask = { afFlexibleWidth, afFlexibleMaxY } sc.onAction do(): @@ -30,46 +30,46 @@ method init(v: ControlsSampleView, r: Rect) = v.addSubview(sc) - let checkbox = newCheckbox(newRect(10, 70, 50, 16)) + let checkbox = newCheckbox(gfx, newRect(10, 70, 50, 16)) checkbox.title = "Checkbox" v.addSubview(checkbox) - let progress = ProgressIndicator.new(newRect(120, 130, v.bounds.width - 130, 16)) + let progress = ProgressIndicator.new(gfx, newRect(120, 130, v.bounds.width - 130, 16)) progress.autoresizingMask = { afFlexibleWidth, afFlexibleMaxY } v.addSubview(progress) - let slider = Slider.new(newRect(120, 70, v.bounds.width - 130, 16)) + let slider = Slider.new(gfx, newRect(120, 70, v.bounds.width - 130, 16)) slider.autoresizingMask = { afFlexibleWidth, afFlexibleMaxY } slider.onAction do(): textField.text = "Slider value: " & $slider.value & " " progress.value = slider.value v.addSubview(slider) - let vertSlider = Slider.new(newRect(v.bounds.width - 26, 150, 16, v.bounds.height - 160)) + let vertSlider = Slider.new(gfx, newRect(v.bounds.width - 26, 150, 16, v.bounds.height - 160)) vertSlider.autoresizingMask = { afFlexibleMinX, afFlexibleHeight } v.addSubview(vertSlider) - let radiobox = newRadiobox(newRect(10, 90, 50, 16)) + let radiobox = newRadiobox(gfx, newRect(10, 90, 50, 16)) radiobox.title = "Radiobox" v.addSubview(radiobox) - let indeterminateCheckbox = newCheckbox(newRect(10, 130, 100, 16)) + let indeterminateCheckbox = newCheckbox(gfx, newRect(10, 130, 100, 16)) indeterminateCheckbox.title = "Indeterminate" indeterminateCheckbox.onAction do(): progress.indeterminate = indeterminateCheckbox.boolValue v.addSubview(indeterminateCheckbox) - let pb = PopupButton.new(newRect(120, 90, 120, 20)) + let pb = PopupButton.new(gfx, newRect(120, 90, 120, 20)) pb.items = @["Popup button", "Item 1", "Item 2"] v.addSubview(pb) sharedAssetManager().getAssetAtPath("cat.jpg") do(i: Image, err: string): - discard newImageButton(v, newPoint(260, 90), newSize(32, 32), i) + discard newImageButton(v, gfx, newPoint(260, 90), newSize(32, 32), i) - let tfLabel = newLabel(newRect(330, 150, 150, 20)) + let tfLabel = newLabel(gfx, newRect(330, 150, 150, 20)) tfLabel.text = "<-- Enter some text" - let tf1 = newTextField(newRect(10, 150, 150, 20)) - let tf2 = newTextField(newRect(170, 150, 150, 20)) + let tf1 = newTextField(gfx, newRect(10, 150, 150, 20)) + let tf2 = newTextField(gfx, newRect(170, 150, 150, 20)) tf1.onAction do(): tfLabel.text = "Left textfield: " & tf1.text tf2.onAction do(): @@ -79,17 +79,17 @@ method init(v: ControlsSampleView, r: Rect) = v.addSubview(tf1) v.addSubview(tf2) - let cp = newColorPickerView(newRect(0, 0, 400, 170)) + let cp = newColorPickerView(gfx, newRect(0, 0, 400, 170)) cp.setFrameOrigin(newPoint(10, 200)) cp.onColorSelected = proc(c: Color) = discard v.addSubview(cp) sharedAssetManager().getAssetAtPath("tile.png") do(i: Image, err: string): - let imageView = newImageView(newRect(0, 400, 300, 150), i) + let imageView = newImageView(gfx, newRect(0, 400, 300, 150), i) v.addSubview(imageView) - let popupFillRule = newPopupButton(v, newPoint(420, 400), newSize(100, 20), ["NoFill", "Stretch", "Tile", "FitWidth", "FitHeight"]) + let popupFillRule = newPopupButton(v, gfx, newPoint(420, 400), newSize(100, 20), ["NoFill", "Stretch", "Tile", "FitWidth", "FitHeight"]) popupFillRule.onAction do(): imageView.fillRule = popupFillRule.selectedIndex().ImageFillRule diff --git a/test/sample03_image.nim b/test/sample03_image.nim index 0c8f0eec9..b9706882e 100644 --- a/test/sample03_image.nim +++ b/test/sample03_image.nim @@ -7,8 +7,8 @@ type ImageSampleView = ref object of View generatedImage: Image httpImage: Image -method init*(v: ImageSampleView, r: Rect) = - procCall v.View.init(r) +method init*(v: ImageSampleView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) loadImageFromURL("http://gravatar.com/avatar/71b7b08fbc2f989a8246913ac608cca9") do(i: Image): v.httpImage = i v.setNeedsDisplay() @@ -17,24 +17,24 @@ method init*(v: ImageSampleView, r: Rect) = v.image = i v.setNeedsDisplay() -proc renderToImage(): Image = +proc renderToImage(gfx: GraphicsContext): Image = + template fontCtx: untyped = gfx.fontCtx let r = imageWithSize(newSize(200, 80)) - r.draw: - let c = currentContext() - c.fillColor = newColor(0.5, 0.5, 1) - c.strokeColor = newColor(1, 0, 0) - c.strokeWidth = 3 - c.drawRoundedRect(newRect(0, 0, 200, 80), 20) - c.fillColor = blackColor() - let font = systemFontOfSize(32) - c.drawText(font, newPoint(10, 25), "Runtime image") + draw gfx, r: + gfx.fillColor = newColor(0.5, 0.5, 1) + gfx.strokeColor = newColor(1, 0, 0) + gfx.strokeWidth = 3 + gfx.drawRoundedRect(newRect(0, 0, 200, 80), 20) + gfx.fillColor = blackColor() + let font = systemFontOfSize(fontCtx, 32) + gfx.drawText(font, newPoint(10, 25), "Runtime image") result = r method draw(v: ImageSampleView, r: Rect) = - if v.generatedImage.isNil: - v.generatedImage = renderToImage() + template gfx: untyped = v.gfx - let c = currentContext() + if v.generatedImage.isNil: + v.generatedImage = renderToImage(gfx) # Draw cat var imageSize = zeroSize @@ -44,15 +44,15 @@ method draw(v: ImageSampleView, r: Rect) = imageRect.origin = imageSize.centerInRect(v.bounds) if not v.image.isNil: - c.drawImage(v.image, imageRect) + gfx.drawImage(v.image, imageRect) # Draw generatedImage imageRect.origin.x += imageSize.width - 60 imageRect.origin.y += imageSize.height - 60 imageRect.size = v.generatedImage.size - c.drawImage(v.generatedImage, imageRect) + gfx.drawImage(v.generatedImage, imageRect) if not v.httpImage.isNil: - c.drawImage(v.httpImage, newRect(50, 50, 100, 100)) + gfx.drawImage(v.httpImage, newRect(50, 50, 100, 100)) registerSample(ImageSampleView, "Image") diff --git a/test/sample04_animation.nim b/test/sample04_animation.nim index 6d7c09e61..c6ba58abf 100644 --- a/test/sample04_animation.nim +++ b/test/sample04_animation.nim @@ -6,12 +6,12 @@ type AnimationSampleView = ref object of View rotation: Coord animation: Animation -method init*(v: AnimationSampleView, r: Rect) = - procCall v.View.init(r) +method init*(v: AnimationSampleView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.animation = newAnimation() # Start/Stop button - let startStopButton = newButton(newRect(20, 20, 50, 50)) + let startStopButton = newButton(gfx, newRect(20, 20, 50, 50)) startStopButton.title = "Stop" startStopButton.onAction do(): if v.animation.finished: @@ -29,7 +29,7 @@ method init*(v: AnimationSampleView, r: Rect) = startStopButton.title = "Start" #v.animation.numberOfLoops = 2 - let playPauseButton = newButton(newRect(80, 20, 70, 50)) + let playPauseButton = newButton(gfx, newRect(80, 20, 70, 50)) playPauseButton.title = "Pause" playPauseButton.onAction do(): if playPauseButton.title == "Pause": @@ -41,7 +41,7 @@ method init*(v: AnimationSampleView, r: Rect) = v.addSubview(playPauseButton) - let progressBar = ProgressIndicator.new(newRect(160, 20, 90, 20)) + let progressBar = ProgressIndicator.new(gfx, newRect(160, 20, 90, 20)) v.addSubview(progressBar) # Loop progress handlers are called when animation reaches specified loop progress. @@ -54,7 +54,7 @@ method init*(v: AnimationSampleView, r: Rect) = v.animation.continueUntilEndOfLoopOnCancel = true method draw(v: AnimationSampleView, r: Rect) = - let c = currentContext() + template c: untyped = v.gfx c.fillColor = newGrayColor(0.5) var tmpTransform = c.transform tmpTransform.translate(newVector3(v.bounds.width/2, v.bounds.height/3, 0)) diff --git a/test/sample05_fonts.nim b/test/sample05_fonts.nim index 4e1986660..0f3c59d2b 100644 --- a/test/sample05_fonts.nim +++ b/test/sample05_fonts.nim @@ -10,10 +10,10 @@ type FontsView = ref object of View baseline: Baseline template createSlider(fv: FontsView, title: string, y: var Coord, fr, to: Coord, val: typed) = - let lb = newLabel(newRect(20, y, 120, 20)) + let lb = newLabel(fv.gfx, newRect(20, y, 120, 20)) lb.text = title & ":" - let s = Slider.new(newRect(140, y, 120, 20)) - let ef = newTextField(newRect(280, y, 120, 20)) + let s = Slider.new(fv.gfx, newRect(140, y, 120, 20)) + let ef = newTextField(fv.gfx, newRect(280, y, 120, 20)) s.onAction do(): let v = fr + (to - fr) * s.value ef.text = $v @@ -32,9 +32,9 @@ template createSlider(fv: FontsView, title: string, y: var Coord, fr, to: Coord, fv.addSubview(ef) y += 22 -method init(v: FontsView, r: Rect) = - procCall v.View.init(r) - let captionTf = newTextField(newRect(20, 20, r.width - 40, 20)) +method init(v: FontsView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) + let captionTf = newTextField(gfx, newRect(20, 20, r.width - 40, 20)) captionTf.autoresizingMask = { afFlexibleWidth, afFlexibleMaxY } captionTf.text = "A Quick Brown $@#&¿" captionTf.onAction do(): @@ -46,7 +46,7 @@ method init(v: FontsView, r: Rect) = var y = 44.Coord v.createSlider("size", y, 8.0, 80.0, v.curFontSize) - let showBaselineBtn = newCheckbox(newRect(20, y, 120, 16)) + let showBaselineBtn = newCheckbox(gfx, newRect(20, y, 120, 16)) showBaselineBtn.title = "Show baseline" showBaselineBtn.onAction do(): v.showBaseline = showBaselineBtn.boolValue @@ -55,7 +55,7 @@ method init(v: FontsView, r: Rect) = v.addSubview(showBaselineBtn) y += 16 + 5 - let baselineSelector = PopupButton.new(newRect(20, y, 120, 20)) + let baselineSelector = PopupButton.new(gfx, newRect(20, y, 120, 20)) var items = newSeq[string]() for i in Baseline.low .. Baseline.high: items.add($i) @@ -66,23 +66,25 @@ method init(v: FontsView, r: Rect) = v.addSubview(baselineSelector) method draw(v: FontsView, r: Rect) = - let c = currentContext() + template gfx: untyped = v.gfx + template fontCtx: untyped = gfx.fontCtx + template gl: untyped = gfx.gl if v.curFont.isNil: - v.curFont = systemFontOfSize(v.curFontSize) - v.curFont.size = v.curFontSize + v.curFont = systemFontOfSize(fontCtx, v.curFontSize) + `size=`(v.curFont, fontCtx, v.curFontSize) - let s = v.curFont.sizeOfString(v.caption) + let s = sizeOfString(fontCtx, gl, v.curFont, v.caption) var origin = s.centerInRect(v.bounds) if v.showBaseline: - c.fillColor = newGrayColor(0.5) - c.drawRect(newRect(origin, newSize(s.width, 1))) + gfx.fillColor = newGrayColor(0.5) + gfx.drawRect(newRect(origin, newSize(s.width, 1))) - c.fillColor = blackColor() + gfx.fillColor = blackColor() let oldBaseline = v.curFont.baseline v.curFont.baseline = v.baseline - c.drawText(v.curFont, origin, v.caption) + gfx.drawText(v.curFont, origin, v.caption) v.curFont.baseline = oldBaseline registerSample(FontsView, "Fonts") diff --git a/test/sample06_timers.nim b/test/sample06_timers.nim index 4809d5e85..61fa62719 100644 --- a/test/sample06_timers.nim +++ b/test/sample06_timers.nim @@ -6,20 +6,20 @@ type TimersSampleView = ref object of View timer: Timer intervalTextField: TextField -method init(t: TimersSampleView, r: Rect) = - procCall t.View.init(r) +method init(t: TimersSampleView, gfx: GraphicsContext, r: Rect) = + procCall t.View.init(gfx, r) - discard t.newLabel(newPoint(20, 20), newSize(120, 20), "interval: ") - let intervalTextField = t.newTextField(newPoint(150, 20), newSize(120, 20), "5") + discard t.newLabel(gfx, newPoint(20, 20), newSize(120, 20), "interval: ") + let intervalTextField = t.newTextField(gfx, newPoint(150, 20), newSize(120, 20), "5") - discard t.newLabel(newPoint(20, 50), newSize(120, 20), "periodic: ") + discard t.newLabel(gfx, newPoint(20, 50), newSize(120, 20), "periodic: ") - let periodicButton = newCheckbox(newRect(150, 50, 20, 20)) + let periodicButton = newCheckbox(gfx, newRect(150, 50, 20, 20)) t.addSubview(periodicButton) var firesLabel: TextField - let startButton = newButton(newRect(20, 80, 100, 20)) + let startButton = newButton(gfx, newRect(20, 80, 100, 20)) startButton.title = "Start" startButton.onAction do(): t.timer.clear() @@ -29,27 +29,27 @@ method init(t: TimersSampleView, r: Rect) = ) t.addSubview(startButton) - let clearButton = newButton(newRect(20, 110, 100, 20)) + let clearButton = newButton(gfx, newRect(20, 110, 100, 20)) clearButton.title = "Clear" clearButton.onAction do(): t.timer.clear() t.addSubview(clearButton) - let pauseButton = newButton(newRect(20, 140, 100, 20)) + let pauseButton = newButton(gfx, newRect(20, 140, 100, 20)) pauseButton.title = "Pause" pauseButton.onAction do(): if not t.timer.isNil: t.timer.pause() t.addSubview(pauseButton) - let resumeButton = newButton(newRect(20, 170, 100, 20)) + let resumeButton = newButton(gfx, newRect(20, 170, 100, 20)) resumeButton.title = "Resume" resumeButton.onAction do(): if not t.timer.isNil: t.timer.resume() t.addSubview(resumeButton) - let secondsLabel = t.newLabel(newPoint(20, 200), newSize(120, 20), "seconds: ") + let secondsLabel = t.newLabel(gfx, newPoint(20, 200), newSize(120, 20), "seconds: ") var secs = 0 setInterval 1.0, proc() = inc secs @@ -59,6 +59,6 @@ method init(t: TimersSampleView, r: Rect) = for i in 0 ..< secs: secondsLabel.text = secondsLabel.text & "O" - firesLabel = t.newLabel(newPoint(20, 230), newSize(120, 20), "fires: ") + firesLabel = t.newLabel(gfx, newPoint(20, 230), newSize(120, 20), "fires: ") registerSample(TimersSampleView, "Timers") diff --git a/test/sample07_collections.nim b/test/sample07_collections.nim index 00045949e..123e4ced3 100644 --- a/test/sample07_collections.nim +++ b/test/sample07_collections.nim @@ -4,18 +4,18 @@ import nimx / [ collection_view, popup_button, slider, text_field, timer, view ] type CollectionsSampleView = ref object of View -method init(v: CollectionsSampleView, r: Rect) = - procCall v.View.init(r) +method init(v: CollectionsSampleView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) setTimeout 0.2, proc() = let collection = @["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven"] - let collectionView = newCollectionView(newRect(0, 0, 450, 250), newSize(50, 50), LayoutDirection.LeftToRight) + let collectionView = newCollectionView(gfx, newRect(0, 0, 450, 250), newSize(50, 50), LayoutDirection.LeftToRight) collectionView.numberOfItems = proc(): int = return collection.len() collectionView.viewForItem = proc(i: int): View = - result = newView(newRect(0, 0, 100, 100)) - discard newLabel(result, newPoint(0, 0), newSize(50, 50), collection[i]) + result = newView(gfx, newRect(0, 0, 100, 100)) + discard newLabel(result, gfx, newPoint(0, 0), newSize(50, 50), collection[i]) result.backgroundColor = newColor(1.0, 0.0, 0.0, 0.8) collectionView.itemSize = newSize(50, 50) collectionView.backgroundColor = newColor(1.0, 1.0, 1.0, 1.0) @@ -23,30 +23,30 @@ method init(v: CollectionsSampleView, r: Rect) = v.addSubview(collectionView) - discard newLabel(v, newPoint(470, 5), newSize(100, 10), "Layout direction:") + discard newLabel(v, gfx, newPoint(470, 5), newSize(100, 10), "Layout direction:") - let popupDirectionRule = newPopupButton(v, newPoint(470, 20), newSize(100, 20), ["LeftToRight", "TopDown"]) + let popupDirectionRule = newPopupButton(v, gfx, newPoint(470, 20), newSize(100, 20), ["LeftToRight", "TopDown"]) popupDirectionRule.onAction do(): collectionView.layoutDirection = popupDirectionRule.selectedIndex().LayoutDirection collectionView.updateLayout() - discard newLabel(v, newPoint(470, 45), newSize(100, 10), "Layout width:") + discard newLabel(v, gfx, newPoint(470, 45), newSize(100, 10), "Layout width:") - let popupLayoutWidth = newPopupButton(v, newPoint(470, 60), newSize(100, 20), ["Auto", "1", "2", "3", "4"]) + let popupLayoutWidth = newPopupButton(v, gfx, newPoint(470, 60), newSize(100, 20), ["Auto", "1", "2", "3", "4"]) popupLayoutWidth.onAction do(): collectionView.layoutWidth = popupLayoutWidth.selectedIndex() collectionView.updateLayout() - discard newLabel(v, newPoint(470, 85), newSize(100, 10), "Item size:") + discard newLabel(v, gfx, newPoint(470, 85), newSize(100, 10), "Item size:") - let popupItemSize = newPopupButton(v, newPoint(470, 100), newSize(100, 20), ["50", "100", "150"]) + let popupItemSize = newPopupButton(v, gfx, newPoint(470, 100), newSize(100, 20), ["50", "100", "150"]) popupItemSize.onAction do(): collectionView.itemSize = newSize((50 + 50 * popupItemSize.selectedIndex()).Coord, (50 + 50 * popupItemSize.selectedIndex()).Coord) collectionView.updateLayout() - discard newLabel(v, newPoint(470, 125), newSize(100, 10), "Offset:") + discard newLabel(v, gfx, newPoint(470, 125), newSize(100, 10), "Offset:") - let offsetField = newTextField(newRect(newPoint(470, 140), newSize(100, 20))) + let offsetField = newTextField(gfx, newRect(newPoint(470, 140), newSize(100, 20))) offsetField.continuous = true offsetField.onAction do(): var offset = try: parseFloat(offsetField.text) except: 0.0 diff --git a/test/sample08_events.nim b/test/sample08_events.nim index ca11c2162..a0e85fe60 100644 --- a/test/sample08_events.nim +++ b/test/sample08_events.nim @@ -23,28 +23,28 @@ type DraggedButton = ref object of Button ######################## -method init(v: ContentView, r: Rect) = - procCall v.View.init(r) +method init(v: ContentView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) -proc newContentView*(frame: Rect): ContentView = +proc newContentView*(gfx: GraphicsContext, frame: Rect): ContentView = result.new() - result.init(frame) + result.init(gfx, frame) -method init(v: ScissorView, r: Rect) = - procCall v.View.init(r) +method init(v: ScissorView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) -proc newScissorView*(frame: Rect): ScissorView = +proc newScissorView*(gfx: GraphicsContext, frame: Rect): ScissorView = result.new() - result.init(frame) + result.init(gfx, frame) method clipType*(v: ScissorView): ClipType = ctDefaultClip -method init(b: DraggedButton, r: Rect) = - procCall b.Button.init(r) +method init(b: DraggedButton, gfx: GraphicsContext, r: Rect) = + procCall b.Button.init(gfx, r) -proc newDraggedButton*(r: Rect): DraggedButton = +proc newDraggedButton*(gfx: GraphicsContext, r: Rect): DraggedButton = result.new() - result.init(r) + result.init(gfx, r) ######################## @@ -96,11 +96,11 @@ method onTapUp*(lis: MyDragListener, dx, dy : float32, e : var Event) = ######################## -method init(v: EventsPriorityView, r: Rect) = - procCall v.View.init(r) +method init(v: EventsPriorityView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) - var scissorView = newScissorView(newRect(0, 25 , 360, 250)) - var contentView = newContentView(newRect(0, 25 , 360, 250)) + var scissorView = newScissorView(gfx, newRect(0, 25 , 360, 250)) + var contentView = newContentView(gfx, newRect(0, 25 , 360, 250)) var sl : MyScrollListener new(sl) sl.updatedView = contentView @@ -111,14 +111,14 @@ method init(v: EventsPriorityView, r: Rect) = for i in 0 .. 10: closureScope: - let button = newButton(newRect(5.Coord, (i * 20).Coord, 150.Coord, 20.Coord)) + let button = newButton(gfx, newRect(5.Coord, (i * 20).Coord, 150.Coord, 20.Coord)) button.title = "Button " & intToStr(i) button.onAction do(): echo "Click ", button.title bttnMesage = "Click " & button.title contentView.addSubview(button) - let button = newButton(newRect(170.Coord, 20, 50.Coord, 50.Coord)) + let button = newButton(gfx, newRect(170.Coord, 20, 50.Coord, 50.Coord)) button.title = "dragged" button.onAction do(): echo "Click ", button.title @@ -132,10 +132,11 @@ method init(v: EventsPriorityView, r: Rect) = method draw(v: EventsPriorityView, r: Rect) = - let c = currentContext() + template gfx: untyped = v.gfx + template fontCtx: untyped = gfx.fontCtx if v.welcomeFont.isNil: - v.welcomeFont = systemFontOfSize(20) - c.fillColor = blackColor() - c.drawText(v.welcomeFont, newPoint(10, 5), bttnMesage) + v.welcomeFont = systemFontOfSize(fontCtx, 20) + gfx.fillColor = blackColor() + gfx.drawText(v.welcomeFont, newPoint(10, 5), bttnMesage) registerSample(EventsPriorityView, "EventsPriority") diff --git a/test/sample09_docking_tabs.nim b/test/sample09_docking_tabs.nim index f3e12b015..e5e306958 100644 --- a/test/sample09_docking_tabs.nim +++ b/test/sample09_docking_tabs.nim @@ -6,14 +6,14 @@ import nimx/editor/tab_view type DockingTabsSampleView = ref object of View tabNameIndex: int -proc newTabTitle(v: DockingTabsSampleView): string = +proc newTabTitle(v: DockingTabsSampleView, gfx: GraphicsContext): string = inc v.tabNameIndex result = "Tab " & $v.tabNameIndex proc newRandomColor(): Color = newColor(rand(1.0), rand(1.0), rand(1.0), 1.0) -proc newTab(v: DockingTabsSampleView): View = - result = View.new(newRect(0, 0, 100, 100)) +proc newTab(v: DockingTabsSampleView, gfx: GraphicsContext): View = + result = View.new(gfx, newRect(0, 0, 100, 100)) result.backgroundColor = newRandomColor() const buttonSize = 20 @@ -26,16 +26,16 @@ proc newTab(v: DockingTabsSampleView): View = return i result = -1 - let addButton = Button.new(newRect(5, 5, buttonSize, buttonSize)) + let addButton = Button.new(gfx, newRect(5, 5, buttonSize, buttonSize)) addButton.title = "+" addButton.onAction do(): let tv = TabView(pane.superview) let i = indexOfPaneInTabView() + 1 - tv.insertTab(i, v.newTabTitle(), v.newTab()) + tv.insertTab(i, v.newTabTitle(gfx), v.newTab(gfx)) tv.selectTab(i) result.addSubview(addButton) - let removeButton = Button.new(newRect(addButton.frame.maxX + 2, 5, buttonSize, buttonSize)) + let removeButton = Button.new(gfx, newRect(addButton.frame.maxX + 2, 5, buttonSize, buttonSize)) removeButton.title = "-" removeButton.onAction do(): let tv = TabView(pane.superview) @@ -45,17 +45,17 @@ proc newTab(v: DockingTabsSampleView): View = tv.removeTab(indexOfPaneInTabView()) result.addSubview(removeButton) - let c = Button.new(newRect(removeButton.frame.maxX + 2, 5, buttonSize, buttonSize)) + let c = Button.new(gfx, newRect(removeButton.frame.maxX + 2, 5, buttonSize, buttonSize)) c.title = "c" c.onAction do(): pane.backgroundColor = newRandomColor() result.addSubview(c) -method init(v: DockingTabsSampleView, r: Rect) = - procCall v.View.init(r) - let pane = TabView.new(v.bounds) +method init(v: DockingTabsSampleView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) + let pane = TabView.new(gfx, v.bounds) pane.dockingTabs = true - pane.addTab(v.newTabTitle(), v.newTab()) + pane.addTab(v.newTabTitle(gfx), v.newTab(gfx)) pane.resizingMask = "wh" pane.userConfigurable = true v.addSubview(pane) diff --git a/test/sample10_text.nim b/test/sample10_text.nim index 1d59e8e8a..55e2d4ac9 100644 --- a/test/sample10_text.nim +++ b/test/sample10_text.nim @@ -27,40 +27,42 @@ iterator rangesOfSubstring(haystack, needle: string): (int, int) = yield (index, b) start = b -method init(v: TextSampleView, r: Rect) = - procCall v.View.init(r) +method init(v: TextSampleView, gfx: GraphicsContext, r: Rect) = + template fontCtx: untyped = gfx.fontCtx + template gl: untyped = gfx.gl + procCall v.View.init(gfx, r) - let tv = TextField.new(v.bounds.inset(50, 50)) + let tv = TextField.new(gfx, v.bounds.inset(50, 50)) tv.resizingMask = "wh" tv.text = textSample tv.backgroundColor = newColor(0.5, 0, 0, 0.5) tv.multiline = true for a, b in tv.text.rangesOfSubstring("Nim"): - tv.formattedText.setFontInRange(a, b, systemFontOfSize(40)) - tv.formattedText.setStrokeInRange(a, b, newColor(1, 0, 0), 5) + setFontInRange(fontCtx, gl, tv.formattedText, a, b, systemFontOfSize(fontCtx, 40)) + setStrokeInRange(fontCtx, gl, tv.formattedText, a, b, newColor(1, 0, 0), 5) for a, b in tv.text.rangesOfSubstring("programming"): - tv.formattedText.setTextColorInRange(a, b, newColor(1, 0, 0)) - tv.formattedText.setShadowInRange(a, b, newGrayColor(0.5, 0.5), newSize(2, 3), 0.0, 0.0) + setTextColorInRange(fontCtx, gl, tv.formattedText, a, b, newColor(1, 0, 0)) + setShadowInRange(fontCtx, gl, tv.formattedText, a, b, newGrayColor(0.5, 0.5), newSize(2, 3), 0.0, 0.0) for a, b in tv.text.rangesOfSubstring("supported"): - tv.formattedText.setTextColorInRange(a, b, newColor(0, 0.6, 0)) + setTextColorInRange(fontCtx, gl, tv.formattedText, a, b, newColor(0, 0.6, 0)) for a, b in tv.text.rangesOfSubstring("Soft Shadow"): - tv.formattedText.setFontInRange(a, b, systemFontOfSize(40)) - tv.formattedText.setShadowInRange(a, b, newColor(0.0, 0.0, 1.0, 1.0), newSize(2, 3), 5.0, 0.8) + setFontInRange(fontCtx, gl, tv.formattedText, a, b, systemFontOfSize(fontCtx, 40)) + setShadowInRange(fontCtx, gl, tv.formattedText, a, b, newColor(0.0, 0.0, 1.0, 1.0), newSize(2, 3), 5.0, 0.8) - let sv = newScrollView(tv) + let sv = newScrollView(gfx, tv) v.addSubview(sv) - let hAlignChooser = SegmentedControl.new(newRect(5, 5, 200, 25)) + let hAlignChooser = SegmentedControl.new(gfx, newRect(5, 5, 200, 25)) hAlignChooser.segments = @[$haLeft, $haCenter, $haRight] v.addSubview(hAlignChooser) hAlignChooser.onAction do(): tv.formattedText.horizontalAlignment = parseEnum[HorizontalTextAlignment](hAlignChooser.segments[hAlignChooser.selectedSegment]) - let vAlignChooser = SegmentedControl.new(newRect(hAlignChooser.frame.maxX + 5, 5, 200, 25)) + let vAlignChooser = SegmentedControl.new(gfx, newRect(hAlignChooser.frame.maxX + 5, 5, 200, 25)) vAlignChooser.segments = @[$vaTop, $vaCenter, $vaBottom] vAlignChooser.selectedSegment = 0 v.addSubview(vAlignChooser) @@ -69,9 +71,9 @@ method init(v: TextSampleView, r: Rect) = tv.formattedText.verticalAlignment = vaTop method draw(v: TextView, r: Rect) = + template gfx: untyped = v.gfx procCall v.View.draw(r) - let c = currentContext() v.text.boundingSize = v.bounds.size - c.drawText(newPoint(0, 0), v.text) + gfx.drawText(newPoint(0, 0), v.text) registerSample(TextSampleView, "Text") diff --git a/test/sample11_expanded_views.nim b/test/sample11_expanded_views.nim index 286a8d0fa..fd6738737 100644 --- a/test/sample11_expanded_views.nim +++ b/test/sample11_expanded_views.nim @@ -5,34 +5,35 @@ import nimx / [ view, font, context, button, expanding_view, stack_view ] type ExpandingSampleView = ref object of View welcomeFont: Font -method init(v: ExpandingSampleView, r: Rect) = - procCall v.View.init(r) +method init(v: ExpandingSampleView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) - let stackView = newStackView(newRect(90,50, 300, 600)) + let stackView = newStackView(gfx, newRect(90,50, 300, 600)) v.addSubview(stackView) for i in 0..4: let rand_y = rand(100 .. 400) - let expView = newExpandingView(newRect(0, 0, 300, rand_y.Coord), true) + let expView = newExpandingView(gfx, newRect(0, 0, 300, rand_y.Coord), true) expView.title = "newExpandedView " & $i stackView.addSubview(expView) for i in 0..4: let rand_y = rand(0 .. 300) - let expView1 = newExpandingView(newRect(0, 0, 300, 10), true) + let expView1 = newExpandingView(gfx, newRect(0, 0, 300, 10), true) expView1.title = "WOW " & $i expView.addContent(expView1) - let testView = newView(newRect(0,0, 100, rand_y.Coord)) + let testView = newView(gfx, newRect(0,0, 100, rand_y.Coord)) testView.backgroundColor = newColor(0.2, 1.2, 0.2, 1.0) expView1.addContent(testView) - discard newButton(testView, newPoint(10, 10), newSize(16, 16), "X") + discard newButton(testView, gfx, newPoint(10, 10), newSize(16, 16), "X") method draw(v: ExpandingSampleView, r: Rect) = - let c = currentContext() + template gfx: untyped = v.gfx + template fontCtx: untyped = gfx.fontCtx if v.welcomeFont.isNil: - v.welcomeFont = systemFontOfSize(20) - c.fillColor = blackColor() - c.drawText(v.welcomeFont, newPoint(10, 5), "test") + v.welcomeFont = systemFontOfSize(fontCtx, 20) + gfx.fillColor = blackColor() + gfx.drawText(v.welcomeFont, newPoint(10, 5), "test") registerSample(ExpandingSampleView, "ExpandingView") diff --git a/test/sample12_menus.nim b/test/sample12_menus.nim index e1cda8ca7..3c7e2fb2b 100644 --- a/test/sample12_menus.nim +++ b/test/sample12_menus.nim @@ -10,12 +10,12 @@ proc leftOf(v: View, width: Coord): Rect = result.size.height = f.height result.size.width = width -method init(v: MenuSampleView, r: Rect) = - procCall v.View.init(r) - let b = Button.new(newRect(5, 5, 100, 25)) +method init(v: MenuSampleView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) + let b = Button.new(gfx, newRect(5, 5, 100, 25)) b.title = "Menu" - let textField = TextField.new(b.leftOf(120)) + let textField = TextField.new(gfx, b.leftOf(120)) textField.text = "Menu: none" let m = makeMenu("File"): diff --git a/test/sample13_drag_and_drop.nim b/test/sample13_drag_and_drop.nim index a43028f5c..f23dacc46 100644 --- a/test/sample13_drag_and_drop.nim +++ b/test/sample13_drag_and_drop.nim @@ -39,53 +39,53 @@ method onDrop*(dd: MyDropDelegate, target: View, i: PasteboardItem) = #============= Views ============== -proc createDraggedView(pos: Point, name: string): View = - result = DraggedView.new(newRect(pos.x, pos.y, 150, 60)) +proc createDraggedView(gfx: GraphicsContext, pos: Point, name: string): View = + result = DraggedView.new(gfx, newRect(pos.x, pos.y, 150, 60)) result.name = name result.backgroundColor = newColor(0.0, 1.0, 0.0, 1.0) - let label_name = newLabel(newRect(2, 0, 200, 40)) + let label_name = newLabel(gfx, newRect(2, 0, 200, 40)) label_name.text = result.name result.addSubView(label_name) -proc createDropView(pos: Point, name: string, delegate: MyDropDelegate): View = - result = newView(newRect(pos.x, pos.y, 200, 200)) +proc createDropView(gfx: GraphicsContext, pos: Point, name: string, delegate: MyDropDelegate): View = + result = newView(gfx, newRect(pos.x, pos.y, 200, 200)) result.name = name result.backgroundColor = newColor(1.0, 0.0, 0.0, 1.0) result.dragDestination = delegate - let label_name = newLabel(newRect(2, 150, 200, 40)) + let label_name = newLabel(gfx, newRect(2, 150, 200, 40)) label_name.text = result.name result.addSubView(label_name) - let label_drop = newLabel(newRect(2, 170, 200, 35)) + let label_drop = newLabel(gfx, newRect(2, 170, 200, 35)) label_drop.text = "drop : " result.addSubView(label_drop) -method init(v: DragAndDropView, r: Rect) = - procCall v.View.init(r) +method init(v: DragAndDropView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) let dropDelegate = MyDropDelegate.new() - let red_view = createDropView(newPoint(50.0, 80.0), "red_drop_view", dropDelegate) + let red_view = createDropView(gfx, newPoint(50.0, 80.0), "red_drop_view", dropDelegate) - let blue_view = createDropView(newPoint(350.0, 80.0), "blue_drop_view", dropDelegate) + let blue_view = createDropView(gfx, newPoint(350.0, 80.0), "blue_drop_view", dropDelegate) blue_view.backgroundColor = newColor(0.0, 0.0, 1.0, 1.0) v.addSubView(red_view) v.addSubView(blue_view) - let draggedView1 = createDraggedView(newPoint(50, 10), "green") + let draggedView1 = createDraggedView(gfx, newPoint(50, 10), "green") v.addSubView(draggedView1) - let draggedView2 = createDraggedView(newPoint(350, 10), "yelow") + let draggedView2 = createDraggedView(gfx, newPoint(350, 10), "yelow") draggedView2.backgroundColor = newColor(1.0, 1.0, 0.0, 1.0) v.addSubView(draggedView2) - let expView = newExpandingView(newRect(50, 300, 200, 400), true) + let expView = newExpandingView(gfx, newRect(50, 300, 200, 400), true) expView.title = "Expanded View " v.addSubview(expView) - let exp_drop_view = createDropView(newPoint(350.0, 80.0), "exp_drop_view", dropDelegate) + let exp_drop_view = createDropView(gfx, newPoint(350.0, 80.0), "exp_drop_view", dropDelegate) exp_drop_view.backgroundColor = newColor(1.0, 0.0, 1.0, 1.0) expView.addContent(exp_drop_view) diff --git a/test/sample14_layout.nim b/test/sample14_layout.nim index 44fe6a326..079448f37 100644 --- a/test/sample14_layout.nim +++ b/test/sample14_layout.nim @@ -8,7 +8,7 @@ type LayoutSampleView = ref object of View type TestView = ref object of View method draw*(v: TestView, r: Rect) = procCall v.View.draw(r) - let c = currentContext() + template c: untyped = v.gfx c.strokeWidth = 2 c.strokeColor = blackColor() let b = v.bounds @@ -44,7 +44,7 @@ reg "Hello world": text: "Hello, world!" y == 10.0 height == 25.0 - - newButton(zeroRect) as mybu: + - newButton(wnd.gfx, zeroRect) as mybu: title: "btn1" onAction do(): echo "hi btn1" @@ -169,12 +169,12 @@ reg "SplitView": - SplitView: frame == inset(super, 10) - - ScrollView: - backgroundColor: blue - size >= [200, 500] - - TestView: - backgroundColor: red - size == [200, 900] + - ScrollView: discard + # backgroundColor: blue + # size >= [200, 500] + # - TestView: + # backgroundColor: red + # size == [200, 900] - TestView: backgroundColor: yellow @@ -200,7 +200,7 @@ reg "Table in SplitView": tableValues.len createCell do() -> TableViewCell: - result = TableViewCell.new(zeroRect) + result = TableViewCell.new(wnd.gfx, zeroRect) result.makeLayout: top == super bottom == super @@ -256,13 +256,13 @@ reg "Autoresizing Frame": frame == autoresizingFrame(NaN, 100, 10, NaN, 100, 10) backgroundColor: green -method init(v: LayoutSampleView, r: Rect) = - procCall v.View.init(r) +method init(v: LayoutSampleView, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) var by = 5'f32 for i in 0 .. testLayoutRegistry.high: closureScope: let c = testLayoutRegistry[i] - let b = Button.new(newRect(10, by, 150, 25)) + let b = Button.new(gfx, newRect(10, by, 150, 25)) b.title = c.name by += 30 b.onAction do(): diff --git a/test/sample15_animation_easings.nim b/test/sample15_animation_easings.nim index cc7bdc54d..26fbae9fe 100644 --- a/test/sample15_animation_easings.nim +++ b/test/sample15_animation_easings.nim @@ -25,8 +25,8 @@ proc createAnimation(v: AnimationEasing, curved: bool): Animation= result = a -method init*(v: AnimationEasing, r: Rect) = - procCall v.View.init(r) +method init*(v: AnimationEasing, gfx: GraphicsContext, r: Rect) = + procCall v.View.init(gfx, r) v.animationLinear = v.createAnimation(false) v.animationCurved = v.createAnimation(true) @@ -92,11 +92,11 @@ method init*(v: AnimationEasing, r: Rect) = v.addSubview(bezierView) - v.progress = ProgressIndicator.new(newRect(50, 400, 550, 20)) + v.progress = ProgressIndicator.new(w.gfx, newRect(50, 400, 550, 20)) v.addSubview(v.progress) method draw(v: AnimationEasing, r: Rect) = - let c = currentContext() + template c: untyped = v.gfx c.strokeWidth = 2 let offsetX = 50.0 diff --git a/ttf b/ttf new file mode 160000 index 000000000..e8a4c0a4a --- /dev/null +++ b/ttf @@ -0,0 +1 @@ +Subproject commit e8a4c0a4a244f164bb0e3f3d2da230deecbb23c6 diff --git a/variant b/variant new file mode 160000 index 000000000..68aab53bf --- /dev/null +++ b/variant @@ -0,0 +1 @@ +Subproject commit 68aab53bf450a51c7d218e7dd6189ee5e3d7e1df