AfxNova progress

Started by José Roca, November 14, 2025, 11:37:56 PM

Previous topic - Next topic

Paul Squires

Wow, that looks seriously impressive!

I am just thinking out loud - how would a FreeBasic programmer get FB data into the Javascript and then from the Javascript back to FB? It would be very convenient to be able to transfer data between the languages because you could use FB for the serious data intensive work and then Javascript for the visual stuff.
Paul Squires
PlanetSquires Software

José Roca

This is done using JSON. WebView2 has methods to send and receive JSON strings. I already have JSON writer and reader classes that work with DWSTRING, BSTRING and DSafeArray. I'm procedeeing step by step because all this is new to me: asynchronous events, html, javascript, JSon... However, my wrapper classes will allow to use a javascript library like Konva as if it was a FreeBasic library, e.g. m_pCanvas->DrawText("text1", 10, 10, "This is a test string", 48, "Arial", "red", "transparent", 0, TRUE). Inside, the function does this:

' ========================================================================================
' Draws a text string on the Konva canvas.
' Parameters:
'   id          : Optional identifier for the text (case-sensitive)
'   x, y        : Coordinates of the text position (in pixels). Use -1, -1 to center the text.
'   dwsText     : The text string to display (Unicode aware)
'   fontSize    : Font size in pixels
'   fontFamily  : Font family name (e.g., "Arial", "Verdana")
'   fillColor   : Text fill color (HTML/CSS color string)
'   strokeColor : Outline color of the text (HTML/CSS color string)
'   strokeWidth : Width of the outline (in pixels)
'   draggable   : If TRUE, the text can be dragged with the mouse
' Description:
'   Creates a new Konva.Text object and adds it to the main layer.
'   If an id is provided, it is assigned to the shape so it can be
'   modified later (e.g., SetShadow, MoveTo, etc.)
' Usage example:
'   m_pCanvas->DrawText("txt1", 100, 150, "Hello world", 24, "Arial", "black", "transparent", 0, TRUE)
' ========================================================================================
PRIVATE SUB CWV2CanvasKonva.DrawText ( _
   BYREF id AS STRING = "", _
   BYVAL x AS LONG, _
   BYVAL y AS LONG, _
   BYREF dwsText AS DWSTRING, _
   BYVAL fontSize AS LONG = 20, _
   BYREF fontFamily AS STRING = "Arial", _
   BYREF fillColor AS STRING = "black", _
   BYREF strokeColor AS STRING = "transparent", _
   BYVAL strokeWidth AS LONG = 0, _
   BYVAL draggable AS BOOLEAN = FALSE)

   DIM idPart AS STRING
   IF id <> "" THEN idPart = "id:'" & id & "',"

   DIM js AS STRING
   js = _
      "var txt = new Konva.Text({" _
      & idPart _
      & "x:0," _
      & "y:0," _
      & "text:" & dwsText.JScript & "," _
      & "fontSize:" & STR(fontSize) & "," _
      & "fontFamily:'" & fontFamily & "'," _
      & "fill:'" & fillColor & "'," _
      & "stroke:'" & strokeColor & "'," _
      & "strokeWidth:" & STR(strokeWidth) & "," _
      & "draggable:" & IIF(draggable, "true", "false") _
      & "});" _
      & "layer.add(txt);" _
      & "var finalX = " & STR(x) & ";" _
      & "var finalY = " & STR(y) & ";" _
      & "var w = txt.width();" _
      & "var h = txt.height();" _
      & "if(finalX == -1){" _
      & "   finalX = (stage.width() - w) / 2;" _
      & "}" _
      & "if(finalY == -1){" _
      & "   finalY = (stage.height() - h) / 2;" _
      & "}" _
      & "txt.x(finalX);" _
      & "txt.y(finalY);" _
      & "layer.draw();"
   this.ExecuteScript(js, NULL)
END SUB
' ========================================================================================

José Roca

dwsText.JScript. JScript This is a property that I have added to DWSTRING to convert its content into a safe JavaScript expression for use with WebView2 ExecuteScript. You know, in the modern web browsers, all is javascript and utf-8.

' ========================================================================================
' JScript (PROPERTY)
' ------------------
' Converts the DWSTRING content into a safe JavaScript expression for use with
' WebView2 ExecuteScript.
'
' WebView2 requires UTF-8 text when passing strings to JavaScript. This property
' takes the internal UTF-8 byte sequence of the DWSTRING and generates a JS
' expression of the form:
'
'     new TextDecoder('utf-8').decode(Uint8Array.from([b1,b2,b3,...]))
'
' This guarantees that any Unicode text (including accents, symbols and emojis)
' is transmitted correctly to JavaScript without escaping issues, broken quotes,
' or encoding mismatches.
'
' The user can therefore pass DWSTRING values directly to ExecuteScript without
' worrying about UTF-8 handling or manual escaping.
'
' Usage example:
' m_pWebView->ExecuteScript("console.log(" & myString.JScript & ");")
'
' Notes:
' - The returned value is valid JavaScript code, not a literal string.
' - Ideal for passing arbitrary text safely from FreeBasic to JavaScript.
' ========================================================================================
' ========================================================================================
PRIVATE PROPERTY DWSTRING.JScript () AS STRING
   DWSTRING_DP("")
   DIM utf8Text AS STRING = this.utf8
   DIM p AS UBYTE PTR = STRPTR(utf8Text)
   DIM js AS STRING = "new TextDecoder('utf-8').decode(Uint8Array.from(["
   DIM L AS LONG = LEN(utf8Text)
   FOR i AS LONG = 0 TO L - 1
      js += STR(p[i])
      IF i < L - 1 THEN js += ","
   NEXT
   js += "]))"
   RETURN js
END PROPERTY
' ========================================================================================

hajubu

#48
hi, just updated  available - Testpan_AfxNova_Web_260629_a0d4caa->related to the Webview2  ( Jose made a commit today ) (+doc 'CRegexClass.md' )

see for available updates in the Forum ...

... Updated AfxNova_Web_260629_a0d4caa

... Tiko 1.3 and AfxNova Docs as integrated HelpTool

PS: if you are you using "AfxNova_webview2_2026.bas" as basis,
please,  do not forget to adapt "AfxNovaWeb.ini".

... ReadMe_AfxNovaWeb2_2026


see also Jose repo at Github for the latest changes
https://github.com/JoseRoca/AfxNova


José Roca

#49
Besides Konva, I'm also adding support for SVG, HTML5 and CSS. We can use HTML5 elemens like buttons, labels, images, videos, audio...

Paul Squires

I have to start thinking of what new application that I can build in order to dive into this new world of webview based applications. Maybe some sort of stock/finance tracker... something that I'd use daily so it'd give be incentive to keep building it. I am back deep into coding Tiko, so another application would be a good distraction so that I don't burn out just coding Tiko.  :-)
Paul Squires
PlanetSquires Software

José Roca

I don't usually write applications anymore. What I enjoy now is building tools that integrate new technologies.

WebView2 offers plenty of technical challenges, and that's exactly what makes it interesting.

The library I'm working on behaves like a synchronous, standard Win32 library — without requiring the developer to write HTML or JavaScript directly.

For events such as click, mousemove, etc., I'm working on a mechanism inside the internal CWebView2 class to register and receive them.

These events will be translated into WM_NOTIFY messages, so they can be handled just like any other native notification in a traditional Win32 application.

José Roca

Added transformers support. A transformer allows to resize and rotate any Konva shape.

hajubu

Hi Jose,

if it doesn't bother you , can you show a small source-sample for the use of konvas-min.js
 ( like you did it in CW_WebView2CanvasKonva.exe (test.zip).

Or is it to early for an example/template ?

Thanks in advance , hajubu

José Roca

#54
It is too early because I first make it to work and then I search for a way to simplify things. Currently you have to implement a navigation completed class, where you can call the Konva wrappers to do the drawing. I intend to implement it as an internal class and write a function called IsNavigationCompleted, so you could use IF pKonva.IsNavigationCompleted THEN... start to draw.

Currently, in my tests I'm using

' ========================================================================================
' Main
' ========================================================================================
FUNCTION wWinMain (BYVAL hInstance AS HINSTANCE, _
                   BYVAL hPrevInstance AS HINSTANCE, _
                   BYVAL pwszCmdLine AS WSTRING PTR, _
                   BYVAL nCmdShow AS LONG) AS LONG

   ' // Set process DPI aware
   SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)
   ' // Enable visual styles without including a manifest file
   AfxEnableVisualStyles

   ' // Create the main window
   DIM pWindow AS CWindow
   DIM hWin AS HWND = pWindow.Create(NULL, "WebView2", @WndProc)
   ' // Set its client size
   pWindow.SetClientSize(600, 400)
   ' // Center the window
   pWindow.Center

   ' // Adds a label
   DIM hLabel AS HWND = pWindow.AddControl("Label", hWin, IDC_LABEL, "", 10, 10, 580, 380)
   ' // Anchors the label to the height and width of the main window
   pWindow.AnchorControl(hLabel, AFX_ANCHOR_HEIGHT_WIDTH)

   ' // Use the same folder for the WebView2 files
   ' // Here we are using a subfolder in the temporary folder
   DIM dwsUserDataFolder AS DWSTRING = AfxGetTempPath & "AfxNovaWebView2"
   ' // Create an instance of the CKonva class
   DIM dwsKonvaPath AS DWSTRING = AfxGetExePath & "/" & "konva.min.js"
'   DIM dwsKonvaPath AS DWSTRING = "https://unpkg.com/konva@10.3.0/konva.min.js"
   DIM pCanvas AS CKonva = CKonva(hLabel, dwsUserDataFolder, dwsKonvaPath)
   ' // Wait until is ready
   IF pCanvas.IsReady THEN
      ' // Set a navigation handler
      DIM pEvt AS CWebView2NavigationCompletedEventHandlerImpl PTR
      pEvt = NEW CWebView2NavigationCompletedEventHandlerImpl(@pCanvas)
   END IF

   ' // Dispatch Windows messages
   FUNCTION = pWindow.DoEvents(nCmdShow)

END FUNCTION
' ========================================================================================

and in the callback...

' ========================================================================================
FUNCTION CWebView2NavigationCompletedEventHandlerImpl.Invoke (BYVAL sender AS Afx_ICoreWebView2 PTR, BYVAL args AS Afx_ICoreWebView2NavigationCompletedEventArgs PTR) AS HRESULT
   CWV2_DP("Navigation completed")
   DIM IsSuccess AS WINBOOL  
   IF args THEN args->get_IsSuccess(@IsSuccess)
   CWV2_DP("IsSuccess: " & WSTR(IsSuccess))
   DIM WebErrorStatus AS COREWEBVIEW2_WEB_ERROR_STATUS
   IF args THEN args->get_WebErrorStatus(@WebErrorStatus)
   CWV2_DP("WebErrorStatus: " & WSTR(WebErrorStatus))
   DIM NavigationId AS UINT64
   IF args THEN args->get_NavigationId(@NavigationId)
   CWV2_DP("NavigationId: " & WSTR(NavigationId))

   IF IsSuccess = FALSE THEN RETURN S_FALSE

DIM dwsPath AS DWSTRING
'dwsPath = "C:\Programs\WinFBX\AfxNova\Examples\WebView2 Examples\Konva\" & "dolomites-2897602_1280.jpg"
'dwsPath = "C:\Programs\WinFBX\AfxNova\Examples\WebView2 Examples\Konva\" & "PIC_001.jpg"
'm_pCanvas->AddImageElement("img1", 0, 0, 580, 380, dwsPath)
'dwsPath = "C:\Programs\WinFBX\AfxNova\Tests\" & "Climber2.jpg"
'm_pCanvas->AddImageElement("img2", 0, 0, 580, 380, dwsPath)


m_pCanvas->DrawRectangle("rect1", 100, 80, 400, 200, "orange", "black", 2, TRUE)
m_pCanvas->AddTransformer("rect1")

   RETURN S_OK

END FUNCTION
' ===================================================================================

In WebView2, everything is asynchronous and in .NET they use the keyword await. When executing my javascript wrappers, I'm using this function to simulate await.

' ========================================================================================
' WaitFor
' ----------------
' Executes a JavaScript condition repeatedly until it becomes true,
' then executes the provided action. Both are JavaScript expressions.
'
' Parameters:
'   conditionJS : JavaScript expression that must return true
'   actionJS    : JavaScript code to execute once the condition is true
'   intervalMS  : milliseconds between checks
'   timeoutMS   : maximum wait time (0 = no timeout)
' ========================================================================================
PRIVATE SUB CKonva.WaitFor (BYREF conditionJS AS STRING, BYREF actionJS AS STRING, _
   BYVAL intervalMS AS LONG = 50, BYVAL timeoutMS AS LONG = 2000)
   DIM js AS STRING
   js = _
      "(function(){" _
      & "   var start = Date.now();" _
      & "   function check(){" _
      & "      try{" _
      & "         if(" & conditionJS & "){" _
      & "            " & actionJS & ";" _
      & "            return;" _
      & "         }" _
      & "      }catch(e){}" _
      & "      if(" & timeoutMS & " > 0 && Date.now() - start > " & timeoutMS & "){" _
      & "         console.error('WaitFor: timeout');" _
      & "         return;" _
      & "      }" _
      & "      setTimeout(check," & intervalMS & ");" _
      & "   }" _
      & "   check();" _
      & "})();"
   this.ExecuteScript(js, NULL)
END SUB
' ========================================================================================

' ========================================================================================
' Applies a radial gradient fill to any shape.
' Parameters:
'   id      : Shape identifier
'   cx, cy  : Center of the gradient
'   r0      : Inner radius
'   r1      : Outer radius
'   stops   : Color stops list, e.g. "0, 'white', 1, 'black'"
' Usage examples:
'   m_pCanvas->AddRadialGradient("circle1", 100, 100, 0, 80, "0, 'white', 1, 'blue'")
'   m_pCanvas->AddRadialGradient("rect1", 50, 50, 10, 120, "0, 'yellow', 1, 'red'")
' ========================================================================================
PRIVATE SUB CKonva.AddRadialGradient (BYREF id AS STRING, BYVAL cx AS DOUBLE, BYVAL cy AS DOUBLE, _
   BYVAL r0 AS DOUBLE, BYVAL r1 AS DOUBLE, BYREF stops AS STRING)
   DIM js AS STRING
   js = _
      "var s = layer.findOne('#" & id & "');" _
      & "if(s){" _
      & "  s.fillRadialGradientStartPoint({x:" & STR(cx) & ", y:" & STR(cy) & "});" _
      & "  s.fillRadialGradientEndPoint({x:" & STR(cx) & ", y:" & STR(cy) & "});" _
      & "  s.fillRadialGradientStartRadius(" & STR(r0) & ");" _
      & "  s.fillRadialGradientEndRadius(" & STR(r1) & ");" _
      & "  s.fillRadialGradientColorStops([" & stops & "]);" _
      & "  s.fillPriority('radial-gradient');" _
      & "  layer.draw();" _
      & "}"
   this.WaitFor("layer.findOne('#" & id & "')", js, 50, 2000)
END SUB
' ========================================================================================

This way, they can be used as if they were synchronous.

hajubu

Hi, Jose

Thank you !

This helps a lot.

b.r.  :-)