Jump to content


Popular Content

Showing content with the highest reputation since 04/29/2020 in all areas

  1. 4 points


    I usually don't publish commercial apps on the forum. Here is the exception : a retail shopping experience - made with Smart
  2. 3 points


    Styling of visual elements. There are some 280 separate css properties which can be used to style each and every visual dom element. The following statement produces such a list var styles := browserapi.window.getComputedStyle(Panel.handle); (280 entries) If we disregard the properties which have a vendor prefix (-webkit, -moz etc) we still end up with some 230 properties for each visual element. And although many of these have default property values, the complexity is just too great to do styling on this level. Each and every css framework (including Smart) tries to reduce the complexity by a number of measures group css properties into meaningful categories, give these groups meaningful names and possibly also rename individual properties connect groups to visual components making sure that all groups adhere to application wide values (i.e. primaryBlue is set to "#ebf8ff" in all groups) eliminate annoying differences in how browsers interpret universal css do this either static and/or dynamic in code The way the above is implemented varies wildly across frameworks. Two examples : Smart does its standard styling mainly by grouping classes on the level of components (1), giving them component names like .TW3Button (2,4). Every visual component gets this class on creation with 2 additional classes for background and border (4). So the resulting styling of a button gets defined by <button class="TW3Button TW3ButtonBackground TW3ButtonBorder" ... The supplied css templates in the Templates directory have a feature which implements application wide setting (5), so f.i. const 'EdgeRounding' will be set to "4px" in all components which use 'EdgeRounding (3)'. There also some settings which standardise browser behaviour (6) Furthermore the rtl has methods to manipulate all classes, groups, properties and stylesheets in code (SmartCL.Css.Classes, SmartCL.Css.Stylesheet (7) Tailwind CCS. This framework is sort of on the other side of the spectrum. It groups css properties in a large numer of very small groups (1,2,3). These groups are almost atomic in nature, so f.i. the 'm-0' class is specified as { margin: 0 }. It basically does not do any of 4,5,6 or 7 So implementing a button using Tailwind involves many classes : <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" which gives a light blue button with rounded corners, becomes a bit darker on hover, has its caption in bold white and has specific values for top/bottom and left/right padding. Nevertheless people build beautifully styled apps and websites using Tailwind. And it is readily accessible through code (7) Thinking about how to merge the best features of the above into a styling design mechanism, I would look at doing this : 1- define a (limited) number of atomic groups. An atomic group combines and names simple css properties which can be used in more complex structures. Candidates are : colors, borders, backgrounds and fonts to start off with. The font-category f.i could be sub-grouped in 'families', 'sizes', 'weights', 'letterSpacings' and 'lineHeights'. As an example the 'lineSpacings' subgroup could then be specified as "letterSpacings": { "tighter": "-0.05em", "tight": "-0.025em", "normal": "0", "wide": "0.025em", "wider": "0.05em", "widest": "0.1em" } 2- define generic building blocks or component groups, i.e a generic button, using as many of the atomic groupings from 1) as possible "button": { "cursor": "pointer", "fontSize": "100%", "lineHeight": "inherit", "backgroundColor": { "colors" : "primary" }, "border": "none", "color": "white", "fontWeight": "bold", "borderRadius": { "radii" : "default" }, ":hover": { "backgroundColor": { "colors" : "primaryHover"}, } This uses the atomic 'colors' group, which will have entries like "primary": "#2b6cb0", "primaryHover": "#2c5282" which then will be substituted in the above, so f.i. the 'backgroundColor' entry will become "backgroundColor": "#2b6cb0" This way all groups who use 'colors:primary' will have the same colour value, application-wide. 3- define descendants from the component groups from 2), f.i. a rounded pill button, which only has those properties different from its parent "buttons": { "pill": { "borderRadius": "full", } } So far the above examples boil down to something like this after substitution (2) and child creation (3) theme01.colors.primary: #2b6cb0 theme01.colors.primaryHover: #2c5282 theme01.buttons.generic.cursor: pointer theme01.buttons.generic.fontSize: 100% theme01.buttons.generic.lineHeight: inherit theme01.buttons.generic.backgroundColor: #2b6cb0 theme01.buttons.generic.border: none theme01.buttons.generic.color: white theme01.buttons.generic.fontWeight: bold theme01.buttons.generic.borderRadius: 0.25rem theme01.buttons.generic.hover.backgroundColor: #2c5282 theme01.buttons.generic.pill.borderRadius: 9999px and a class invocation like <button class="buttonPill ..." (or even TW3Button) 4- generate this into a regular css template and include the modernize.css entries to harmonize across browsers as well. 5- integrate this in the visual designer Actually the style generation should closely follow the hierarchy in the visual designer. So if there is a rounded-button on a panel on a form on the <body> element, then the css property values should reflect the #body, #form, #panel, #generic-button and #rounded-button properties (and nothing more). just an idea, but doable
  3. 3 points

    TW3StringGrid is available

    It's not a good idea to try to hack this by using GetChildById and changing styles. Using BackgroundThemes is one way of doing it. But if you want complete control over how a cell is rendered, make your own column class from TW3StringGridTextColumn and override the GenerateHtml -function in it. That way you can use every trick there is in css. For example: TW3MyCoronaColumn = class(TW3StringGridTextColumn) public function GenerateHtml(GridLine: TW3RenderedGridLine; ColumnIndex, CurrentLeft: Integer): String; override; end; ... function TW3MyCoronaColumn.GenerateHtml(GridLine: TW3RenderedGridLine; ColumnIndex, CurrentLeft: Integer): String; begin var ItemId:=Grid.Handle.id+'_row_'+IntToStr(GridLine.DataIndex)+'_col_'+IntToStr(ColumnIndex); GridLine.ItemIds.Add(ItemId); result:='html code for the cell'; end; You can have a look at how TW3StringGridTextColumn generates the html as an example.
  4. 3 points

    https://peerjs.com/ - is this feasible?

    just as a proof of concept : tutor initiates conversation with student : https://lynkfs.com/Experiments/peer/receive/www and on another machine : https://lynkfs.com/Experiments/peer/send/www (must be on another machine) student sends a text by copying tutor id into the edit box and pressing buttons 1, 2 and 3 this text then arrives at the tutors project The user interface of these projects is really bad, sorry, just proof-of-concept. This demo is basically a simple chap app, but since it is using webrtc (and webrtc handles not only text but also other media), I feel that extending this to a full-fledged channel between parties should be feasible and reasonably straightforward
  5. 2 points


    I recently came across the javascript proxy object A proxy object basically monitors a chosen object, and has getter and setter functions which are invoked when the value of any of its properties changes (set) or are being accessed (get). In that respect there are some similarities with the mutationobserver object. In the below code snippet a regular EditBox has its text saved in a proxied object, which triggers the setter function and a Button which reads that value and thus triggers the getter function procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components var Edit1 : TW3EditBox := TW3EditBox.Create(self); Edit1.SetBounds(32,40,300,32); var Button1 : TW3Button := TW3Button.Create(self); Button1.SetBounds(340,40,200,32); Button1.Caption := 'read'; var Memo1 : TW3Memo := TW3Memo.Create(self); Memo1.SetBounds(32,90,300,100); //set proxies var MyProxy : variant := new JObject; var ProxyObj : variant := new JObject; ProxyObj.EditBox1 := 'initialvalue'; //will be overwritten Edit1.OnInput := lambda MyProxy.EditBox1 := Edit1.Text; end; //triggers 'handler.set' Button1.OnClick := lambda Button1.Caption := MyProxy.EditBox1; end; //triggers 'handler.get' var handler : variant := new JObject; handler.get := function(obj, prop: variant): variant begin Memo1.Add('someone read ' + prop + ' as ' + obj[prop] + #10); result := obj[prop]; end; handler.set := function(obj, prop, value: variant):variant begin Memo1.Add('someone set ' + prop + ' to ' + value + #10); obj[prop] := value; result := true; end; asm @MyProxy = new Proxy(@ProxyObj,@handler); end; end; This mechanism could f.i. be used to bind form components with a data source, updating them automatically when data changes or is being accessed Demo
  6. 2 points

    https://peerjs.com/ - is this feasible?

    Just going through the first bit of their docs, it looks fairly straightforward procedure TForm1.W3Button1Click(Sender: TObject); begin // var Script := browserapi.document.createElement('script'); Script.src := 'https://cdn.jsdelivr.net/npm/peerjs@0.3.20/dist/peer.min.js'; browserapi.document.head.appendChild(Script); Script.onload := procedure begin writeln('peer.js loaded'); asm @mypeer = new Peer({key: 'lwjd5qra8257b9'}); end; //demo key mypeer.on('open', procedure(id:string) begin writeln('New peer ID : ' + id); end); end; end; the remainder doesn't look too cumbersome, i'll have a look later
  7. 2 points
    Another important fix to handling of aligns is available.
  8. 2 points
    Yes, if I make the change and comment out that line, the items do align correctly on the initial form create. And if I leave that line enabled, you are correct that when I resize the window, they do draw correctly. Now that you know where the issue is, I feel great that you'll come up with a solution to accommodate. The progress you have made recently with this stuff is great and I have been enjoying working with this . And to be honest, I am using the Alpha track for production development and I had just gotten used to not leaving a way to revert back to the prior version. This time it got me. <grin> Thanks for all you do.
  9. 1 point

    Scroll form

    I'm not a js guru , or maybe Anyway, logically it's either the form or the memo which reacts to scrolling/swiping. If you want both, you need to be able to distinguish in which situations you want either to happen. There are various ways to do this, one of them this : as soon as the form scrolls, disable scrolling on the memo altogether. Then you need to determine under which condition you want to re-enable scrolling the memo. One way would be to have a simple onclick handler procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components self.NativeScrolling := true; self.onscroll := lambda W3Memo1.NativeScrolling := false; end; W3Memo1.OnClick := lambda W3Memo1.NativeScrolling := true; end; demo here Instead of having to click the memo, it would be better to do this automatically when form-scrolling has ended. There is no such thing as a scroll-end event, but you could simulate one by using a timer of say 1000 ms. Reset the timer continuously during form scrolling and re-enable memo scrolling after the timer fires. This may work for you, but it is hooking into some finely tuned browser actions, so look out for surprises.
  10. 1 point


    Looks interesting, I think it could be implemented as partial class and added to RTL just like with MouseTouch.pas
  11. 1 point


    Every now and then I read something so refreshing, that it jolts the old braincells out of its rut. 10/GUI describes a new gui system concept which does away with keyboards and mice, relying on gestures instead Fun, and somehow very convincing Recently I needed a mechanism to cycle through an image carousel. There are different ways of doing this, but for touch enabled devices I opted for a one-finger swipe gesture. W3Image.OnSwipeLeft := procedure(sender: TObject) begin ... end; The implementation of OnSwipeLeft below is somewhat project specific, but includes the following - it is made a property of all customcomponents descendants (helper construct) - it extends TNotifyEvent, so it does not only return the sending object (sender: TObject), but also the event object (event: variant). That way OnSwipeLeft has access to properties of both (and handles all event-types) - it checks if the device has touch capabilities and uses the touch-api if that is the case, otherwise it uses pointerevents Demo project (covering all swipe orientations. Verbose code, can be simplified) The touch-api supports multi-touch events to a certain maximum. My phone can handle 5 fingers. That makes it possible to implement for instance two-finger swipes to slide-in the next form. Form.OnFormSwipeRight := lambda Application.GotoForm('Form2', feFromRight); end;
  12. 1 point

    https://peerjs.com/ - is this feasible?

    webrtc I think has come of age, so it's worthwhile exploring I'll be happy to have a look
  13. 1 point

    SmartMS intro with ThreeJS and p5.js

    Edited The other approach is to gain access to the internal j5 functions in a more readable way, as in function createElement(a,b:string): Variant; external 'createElement'; function ellipse(a,b,c,d:integer): Variant; external 'ellipse'; implementation { TForm1 } procedure TForm1.W3Button1Click(Sender: TObject); begin var txt := createElement('p', 'move your mouse over the canvas'); txt.position(50, 70); W3DivHtmlElement1.onMouseMove := procedure(Sender: TObject; Shift: TShiftState; X: Integer; Y: Integer) begin ellipse(X,Y,80,80); end; which eliminates the asm blocks (code relates to Demo1 project) and to top it all off : here is a version which does away with all of the above and previous, does not need a separate sketch.js file, custom template etc. and does everything in code unit Form1; interface uses System.Types, System.Types.Convert, System.Objects, System.Time, System.IOUtils, System.Device.Storage, SmartCL.System, SmartCL.Time, SmartCL.Graphics, SmartCL.Components, SmartCL.FileUtils, SmartCL.Device.Storage, SmartCL.Forms, SmartCL.Fonts, SmartCL.Theme, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.Button, SmartCL.Controls.Elements; type TForm1 = class(TW3Form) procedure W3Button1Click(Sender: TObject); private {$I 'Form1:intf'} protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; end; var myp5 : variant; implementation { TForm1 } procedure TForm1.W3Button1Click(Sender: TObject); begin // var Script := browserapi.document.createElement('script'); Script.src := 'https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js'; browserapi.document.head.appendChild(Script); Script.onload := procedure begin writeln('p5.js loaded'); var s : variant := new(JObject); var sketch : variant := new(JObject); //set up the sketch object and create its canvas s := procedure(sketch:variant) begin sketch.setup := procedure() begin sketch.createCanvas(W3DivHtmlElement1.width, W3DivHtmlElement1.height); end; sketch.draw := procedure() begin //any initial draw activities //sketch.rect(10,10,50,50); end; end; // create the p5 instance and link it to the panel var x := W3DivHtmlElement1.handle.id; asm @myp5 = new p5(@s, @x); end; //use the p5 instance W3DivHtmlElement1.onMouseMove := procedure(Sender: TObject; Shift: TShiftState; X: Integer; Y: Integer) begin myp5.fill('pink'); myp5.ellipse(X,Y,80,80); end; myp5.rect(100,100,50,50); end; end; procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components W3DivHtmlElement1.handle.style.border := '1px solid red'; end; procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} end; procedure TForm1.Resize; begin inherited; end; initialization Forms.RegisterForm({$I %FILE%}, TForm1); end. this way you could even have multiple canvasses on the same form
  14. 1 point

    SmartMS intro with ThreeJS and p5.js

    Demo1 and Demo2 P5.js : There are different ways of doing this - mapping all p5.js functions and variables to pascal so they can be accessed from Smart by name - take a bit of a shortcut The latter approach looks like this : - let P5 construct its canvas. This requires a minimum sketch.js file with its setup and draw functions. (The draw function can be empty. See project zipfile demo1) - copy the constructed canvas to a Smart component (W3Panel); - use p5 functions in code Demo1 uses this approach, Demo2 is a copy of one of the p5 examples Both projects in this zipfile
  15. 1 point
    This is now fixed in the Alpha channel
  16. 1 point
    @Tim Koscielski Thank you. I can reproduce the problem. Resizing the form will do, which means that something goes wrong during the initial resize right after the form has been created. Gonna have a look. Edit: Can you try removing this from line 4595 in SmartCL.Components.pas and see if it fixes the problem? RemoveFromComponentState([csAligning]); //Remove this line
  17. 1 point

    video in embedded browser

    The MP4 codecs are only license free in browsers (as long as users don't have to pay for the audio or video). In any other application the use of the most widely used MP4 codecs requires a commercial license. Looks like that is the reason for the exclusion of mp4 in CEF4 It should be possible to write-your-own MP4 codec. There are a couple of interesting posts on the subject (post 1, post 2) and there is a rudimentary codec structure in the rtl (system.codec.) However not in the complexity of what mp4 requires Google has produced its own license free product : 'webm' This is used in their own products (youtube) and by other main producers (netflix) There are many online converters available to convert .mp4 files to .webm files Using the TW3MediaPlayer component (smartcl.controls.mediaplayer) to see how that works : procedure TForm1.InitializeForm; var VPlayer : TW3MediaPlayer; begin inherited; // this is a good place to initialize components VPlayer := TW3MediaPlayer.Create(self); VPlayer.SetBounds(20,20,800,500); if VPlayer.CanPlayType('video/webm','vp8, vp9') = msProbably then writeln('probably'); if VPlayer.CanPlayType('video/webm','vp8, vp9') = msMaybe then writeln('maybe'); if VPlayer.CanPlayType('video/webm','vp8, vp9') = msNo then writeln('no'); VPlayer.Load('video1.webm'); //video1.mp4'); var Button1 := TW3Button.Create(self); Button1.SetBounds(20,550,100,30); Button1.Caption := 'Play'; Button1.OnClick := lambda VPlayer.Play; end; end; And indeed this plays videos in both standalone browsers and the ide
  • Create New...