Jump to content

Leaderboard


Popular Content

Showing content with the highest reputation since 03/05/2019 in all areas

  1. 7 points
    jarto

    TW3StringGrid is available

    A new update is available in the development-channel of SmartUpdate. It contains a completely new Grid: TW3StringGrid. It's a completely new design, which was made to be very fast and able to handle lots of data. This is achieved through dynamic drawing: Only the visible grid lines are rendered. There is a new StringGrid-demo in the Featured Demos. It downloads a json file and populates the grid with data from it. TW3StringGrid supports six different column types: Text (column class TW3StringGridTextColumn) Numeric (column class TW3StringGridNumericColumn) EditBox (column class TW3StringGridEditColumn) ComboBox (column class TW3StringGridComboColumn) Checkmark (column class TW3StringGridCheckmarkColumn) Button (column class TW3StringGridButtonColumn) Columns are created by calling: MyNewColumn:=Grid.AddColumn(ColumnClass); If no ColumnClass is given, a Text column is created. Column properties: Caption Width BorderType Default is btLightBorderRight. Set to btNone if you don't want any vertical borders. Backgroundtype Default is btNone to let the line color through. AlignText (The same way as in TW3Label) taLeft (default) taCenter taRight SelectOptions (for TW3StringGridComboColumn only) This is an array of selectable values. First value (index 0) is the value for no selection. For example: SelectOptions:=['','First','Second','Third']; RowBorderType: Default is btLightBorderBottom. Set to btNone if you don't want any horizontal borders. RowBackgroundType: Default is bsListItemBackground. Set to bsDecorativeListItemBackground (or test other backgrounds) to change the background style for even rows. RowOddBorderType: Default is btLightBorderBottom. Set to btNone if you don't want any horizontal borders. Affects odd rows. RowOddBackgroundType: Default is bsListItemBackground. Set to bsDecorativeListItemBackground (or test other backgrounds) to change the background style for odd rows. Grid properties: RowCount: Set number of rows to show FixedColumns: How many columns should be fixed (aka not scrollable horizontally) LineHeight MultiSelect Events: OnCellClick: Is triggered when the row is clicked. OnCellChanged: Is triggered when cell content changes through editing (for example: through editing) OnDrawGridLineTheme: Can be used to set custom backgrounds and borders for a row. (for example, set background to bsErrorBackground for lines that should be highlighted to the user) Methods: InvalidateGrid: Triggers a repaint of the grid. Sort Sorting TW3StringGrid supports sorting based on one of multiple columns. Clicking on the column headers sets or reverses sort order. You can also control sorting in code: procedure AddSortColumn(Index: Integer; SortOrder: TW3SortOrder = soNormal); overload; procedure AddSortColumn(Col: TW3StringGridColumn; SortOrder: TW3SortOrder = soNormal); overload; procedure SetSortColumn(Index: Integer; SortOrder: TW3SortOrder = soNormal); overload; procedure SetSortColumn(Col: TW3StringGridColumn; SortOrder: TW3SortOrder = soNormal); overload; procedure ToggleSortColumn(Index: Integer); For example: Grid.SortOptions.AddSortColumn(3,soReverse); //Set primary sort column Grid.SortOptions.AddSortColumn(1); //Add secondary sort columns Grid.Sort; Working with sorted data When the Grid is sorted, it only affects how data is shown. Sorting does not change Grid data itself at all. When you work with a sorted grid events and indexes (for example SelectedIndex) always refer to the index in the grid data itself and NOT the visible line number. TW3StringGrid uses these events: TW3StringGridColumnEvent = procedure(const Sender: TW3StringGridColumn; const Row: Integer); TW3StringGridEvent = procedure(const Sender: TObject; const Row, Col: Integer); If you have a Grid where "Australia" is on line 2, SelectedIndex:=2 selects that row regardless of how the grid is sorted. Clicking on that row or changing data on that row also returns Row=2 regardless of how the grid is sorted.
  2. 5 points
    jarto

    UI layout

  3. 4 points
    lynkfs

    Responsive Design

    Responsive design in essence consists of 2 measures : style all components as best as possible depending on screen size (larger buttons on mobile, proportional font sizing etc) tweak the layout of forms so that it works best on any given screen size 1) Screen size dependent styling can be done in code and/or is done as other frameworks do by including media queries in the stylesheet. Media queries basically activate different parts of a stylesheet depending on the actual screen size. Smart doesn't implement this in its shipped stylesheets, but there is no reason it can't be done. Just a bit of work. 2) Tweaking the form layout in Smart is possible by doing it in code depending on screensize and orientation - a bit of a pain really using the Layout Manager using some css grid framework or ui kit. For website development I've used the Frow framework quite a bit, but there are many others. using anchors and the CSS grid (which is basically the topic of this post) The new anchors are fantastic, but don't really help in responsive design. Browser window resizing doesn't proportionally resize anchored components. That is unless they are dimensioned depending on screen size beforehand : var W3Panel0 : TW3Panel := TW3Panel.Create(self); W3Panel0.Left := trunc(browserapi.window.innerWidth*0.05); W3Panel0.Top := trunc(browserapi.window.innerHeight*0.05); W3Panel0.Width := trunc(browserapi.window.innerWidth*0.9); W3Panel0.Height := trunc(browserapi.window.innerHeight*0.10); W3Panel0.Anchors := [akLeft, akRight, akTop]; // var W3Panel1 : TW3Panel := TW3Panel.Create(self); W3Panel1.Left := trunc(browserapi.window.innerWidth*0.05); W3Panel1.Top := trunc(browserapi.window.innerHeight*0.15); W3Panel1.Width := trunc(browserapi.window.innerWidth*0.9); W3Panel1.Height := trunc(browserapi.window.innerHeight*0.70); W3Panel1.Anchors := [akLeft, akRight, akTop, akBottom]; W3Panel1.NativeScrolling := true; // var W3Panel2 : TW3Panel := TW3Panel.Create(self); W3Panel2.Left := trunc(browserapi.window.innerWidth*0.05); W3Panel2.Top := trunc(browserapi.window.innerHeight*0.85); W3Panel2.Width := trunc(browserapi.window.innerWidth*0.9); W3Panel2.Height := trunc(browserapi.window.innerHeight*0.10); W3Panel2.Anchors := [akLeft, akRight, akBottom]; As an example the code above produces a header (always on top), a footer (always on the bottom) and a scrollable body inbetween with some proportional margins. The good thing is it works well on all screen sizes, and even on orientation changes, without having to code anything else. Instead of using a grid system with media queries, or doing it in code, it is also possible to use the built-in css grid to get a responsive design. Much easier, see article. If I just plonk the css and html of the first example in that article into the structure above, this is how it ends up looking : Demo Resize browser window to see this in effect (or see it in the responsinator) Not the end-all of all possible responsive design options, but it may fit the bill sometimes. Note : the built-in chrome browser doesn't understand the css grid, so execute from file or server.
  4. 4 points
    Czar

    UI layout

    For me the most frustrating aspect of using SMS is layouts. Putting together anything beyond a trivial UI takes ages and is very frustrating to achieve. The Tlayout is not intuitive, it has weird and unexplainable side effects and takes a long time to get it right. Once you have it working then generally it is fine. We really need to have a more intuitive system to allow easy to create and maintain UI. I believe simple anchor (left, top, right, bottom)m, Align and "alignWithMargin" like Delphi has would be a great bonus to SMS. I have no idea what would be involved, or how much work it would be, but I would love to see this being developed.
  5. 4 points
    jarto

    UI layout

    The development-channel contains a new update that adds Align and Anchor-support in the RTL. We still have some work to do to get the Visual Designer and Object Inspector to support these properly. While we work on that, you can still use these by setting anchors in code. Align can actually be set in the Object Inspector, but do note that setting the alignment does not make any visual changes in the Visual Designer. I'm going to be travelling for a bit more than a week.. During that time I'll be able to participate in this forum but can't make any changes or bug fixes. If you happen to find situations when Anchors and Align does not work properly, please post examples. I'll be happy to have a look when I return from my trip.
  6. 4 points
    lynkfs

    JQWidget integration

    and here is the Grid - JQWidgets implementation working sms demo here Notes : there are 2 different formats of defining the grid, one using jQuery explicitly and one as a web-component. The latter format has a bug somewhere and comes up consistently with a runtime error. The former format works fine. the 'export to Excel' button actually fires up Excel if installed and run locally (sms demo is grid only but take my word)
  7. 4 points
    lynkfs

    JQWidget integration

    I got asked to have a look at how to integrate jqxWidgets in SMS There are a heap of ready-made widgets under the name of jQuery UI Widgets, ranging from grids to calendars to tooltips and many more. The example here is a jqxButton with an image in the left top corner. The vanilla html file for this looks like <!DOCTYPE html> <html lang="en"> <head> <meta name="keywords" content="jQuery Button, CheckBox, Toggle Button, Repeat Button, Radio Button, Link Button, Button" /> <meta name="description" content="The jqxButton widget allows you to display a button on the Web page." /> <title id='Description'>The jqxButton widget allows you to display a button on the Web page.</title> <link rel="stylesheet" href="../../jqwidgets/styles/jqx.base.css" type="text/css" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta name="viewport" content="width=device-width, initial-scale=1 maximum-scale=1 minimum-scale=1" /> <script type="text/javascript" src="https://www.jqwidgets.com/jquery-widgets-demo/scripts/jquery-1.11.1.min.js"></script> <script type="text/javascript" src="https://www.jqwidgets.com/jquery-widgets-demo/jqwidgets/jqxcore.js"></script> <script type="text/javascript" src="https://www.jqwidgets.com/jquery-widgets-demo/jqwidgets/jqxbuttons.js"></script> <script type="text/javascript"> $(document).ready(function () { // Create jqxButton widgets. $("#jqxButton").jqxButton({ width: 120, height: 40 }); // Subscribe to Click events. $("#jqxButton").on('click', function () { $("#events").find('span').remove(); $("#events").append('<span>Button Clicked</span'); }); }); </script> </head> <body class='default'> <div> <input type="button" value="Button" id='jqxButton' /> </div> <div style='font-size: 12px; font-family: Verdana; margin-top: 10px;'> <div>Events:</div> <div id='events'> </div> </div> </body> </html> which requires a number of external js and css files. These can be local or reside on a cdn server somewhere. Make sure they are served from either http or https, same as the platform the app is executed on. The widgets are created first, immediately after the document is dom-ready, and they will be rendered on a placeholder element with the same element.id as the created widgets. This can be simplified considerably in SMS : All functions can be recoded using this function tied to the ubiquitous jQuery "$" directive : function jqxQuery(aTagObj: string): Variant; external '$'; which results in this code : var props : variant := new JObject; props.height := 40; props.width := 120; props.imgSrc := "https://www.jqwidgets.com/jquery-widgets-demo/images/twitter.png"; props.imgPosition := "topLeft"; jqxQuery( "#jqxButton1" ).jqxButton(props); jqxQuery( "#jqxButton1" ).on('click', procedure() begin browserapi.window.alert('clicked button1'); end); and the placeholder element jqxButton1 := TW3Panel.Create(self); jqxButton1.SetBounds(30,20,126,46); jqxButton1.handle.style.border := '1px solid grey'; jqxButton1.handle.innerHTML := '<input type="button" value="Button" id="jqxButton1" />'; Sort of a rough and tumble approach, but it does work. A better way is to encapsulate these widgets in a dedicated component : unit jqxButton; { **************************************************************************** } { } { Smart Mobile Studio } { } { **************************************************************************** } interface {$I 'Smart.inc'} uses System.Types, {$IFDEF THEME_AUTOSTYLE} SmartCL.Theme, {$ENDIF} SmartCL.System, SmartCL.Components; type TJQXButton = class(TW3CustomControl) protected function MakeElementTagObj: THandle; override; procedure InitializeObject; override; published property caption: string; // read GetCaption write SetCaption; end; function jqxQuery(aTagObj: string): Variant; external '$'; implementation //############################################################################# // TJQXButton //############################################################################# procedure TJQXButton.InitializeObject; begin inherited; Caption := 'Button'; //default Handle.ReadyExecute( procedure () begin var Script := browserapi.document.createElement('script'); Script.src := 'https://www.jqwidgets.com/jquery-widgets-demo/jqwidgets/jqxcore.js'; browserapi.document.head.appendChild(Script); Script.onload := procedure begin writeln('jqxcore loaded'); var props : variant := new JObject; props.height := self.height-2; props.width := self.width-2; props.imgSrc := "https://www.jqwidgets.com/jquery-widgets-demo/images/twitter.png"; props.imgPosition := "topLeft"; jqxQuery( "#jqx" + self.handle.id ).jqxButton(props); end; handle.innerHTML := '<input type="button" value="' + Caption + '" id="jqx' + self.handle.id + '" />'; end); end; function TJQXButton.MakeElementTagObj: THandle; begin result := w3_createHtmlElement('div'); end; end. which can be instantiated as simple as jqxButton2 := TJQXButton.Create(self); jqxButton2.SetBounds(230,20,130,50); jqxButton2.Caption := 'Button2'; jqxButton2.OnClick := procedure(Sender:TObject) begin browserapi.window.alert('clicked button2'); end; Project demo here and source code here
  8. 4 points
    jarto

    Beware of box shadows!

    I have been working on a new TW3StringGrid for Smart Mobile Studio. The original plan was to include it in 3.0, but there was a problem: It was slow on iOS. So, ever since the release, I've tried to crack this problem. How can Javascript that works beautifully on every other platform and browser be absolutely horrible on iOS? It was extremely fast on desktop browsers and Android tablets. On iOS it took seconds to draw the grid and scrolling it was close to unusable. After countless of hours of testing and trying, I finally found the reason: Box shadows in CSS. For example: .TW3ContainerBorder { border-radius: <?pas=EdgeRounding?>; border-top: 1px solid rgba(250, 250, 250, 0.7); border-left: 1px solid rgba(250, 250, 250, 0.7); border-right: 1px solid rgba(240, 240, 240, 0.5); border-bottom: 1px solid rgba(240, 240, 240, 0.5); -webkit-box-shadow: 0px 0px 1px 1px <?pas=GetRGBA(clDlgShadowBaseColor, 0.3)?>; -moz-box-shadow: 0px 0px 1px 1px <?pas=GetRGBA(clDlgShadowBaseColor, 0.3)?>; -ms-box-shadow: 0px 0px 1px 1px <?pas=GetRGBA(clDlgShadowBaseColor, 0.3)?>; -o-box-shadow: 0px 0px 1px 1px <?pas=GetRGBA(clDlgShadowBaseColor, 0.3)?>; box-shadow: 0px 0px 1px 1px <?pas=GetRGBA(clDlgShadowBaseColor, 0.3)?>; } .TW3FlatBorder { border-radius: <?pas=EdgeRounding?>; border-top: 1px solid rgba(44, 44, 44, 0.8); border-left: 1px solid rgba(44, 44, 44, 0.8); border-right: 1px solid rgba(44, 44, 44, 0.8); border-bottom: 1px solid rgba(44, 44, 44, 0.8); } I had used a TW3ContainerBorder for every cell in the grid. Replacing them with a flat border improved performance dramatically on iOS. Something like an order of magnitude faster. So if you've been having performance problems with iOS, that's most likely the reason.
  9. 3 points
    jarto

    Development updates

    New update available in the development-channel: Anchors and Align-support to the RTL. Bug fix to BoundsRect Please note that anchors can only be set in code at the moment. The next step is to add support for setting the anchors and align in the IDE's designer as well. Currently Align actually can be set in the Object Inspector, but the designer does not show the result by aligning components.
  10. 3 points
    DavidRM

    Node.JS WebSocket Client Socket

    This is me sharing again. The NodeJS WebSocket server socket is implemented in SMS. But I didn't find an implementation of the WebSocket client socket. Sometimes an SMS NodeJS server needs to connect to another server. So I kinda hacked this together using the server socket as a guide. unit UPBNCommonNJWebSocket; interface uses System.Types, System.Types.Convert, System.Time, System.Streams, System.Reader, System.Writer, System.Device.Storage, System.Objects, SmartNJ.System, SmartNJ.Streams, SmartNJ.Device.Storage, SmartNJ.Application, NodeJS.Core, NodeJS.WebSocket, SmartNJ.Server.WebSocket; type // Forward declarations TNJWebSocket = class; TNJWebSocketOpenEvent = procedure (Sender: TNJWebSocket); TNJWebSocketCloseEvent = procedure (Sender: TNJWebSocket; Code: integer; const Reason: string); TNJWebSocketErrorEvent = procedure (Sender: TNJWebSocket; Error: TJSErrorObject); TNJWebSocketMessageEvent = procedure (Sender: TNJWebSocket; Message: TNJWebsocketMessage); TNJWebSocket = class(TW3ErrorObject) private FSocket: JWsSocket; public property WSSocket: JWsSocket read FSocket; function SocketState: JWsReadyState; function Connected: boolean; function URL: string; function Protocol: string; procedure Connect(URL: string; Protocols: array of string); overload; procedure Connect(URL: string); overload; procedure Disconnect; overload; procedure Send(const Data: variant); overload; procedure Send(const Text: string); overload; procedure Send(const Data: TStream); overload; property TagData: variant; constructor Create; override; destructor Destroy; override; procedure Ping; published property OnOpen: TNJWebSocketOpenEvent; property OnClosed: TNJWebSocketCloseEvent; property OnMessage: TNJWebSocketMessageEvent; property OnError: TNJWebSocketErrorEvent; end; implementation constructor TNJWebSocket.Create; begin inherited Create; // We dont want to throw exceptions whenever SetLastError() is called ErrorOptions.ThrowExceptions := false; end; destructor TNJWebSocket.Destroy; begin FSocket := nil; inherited; end; procedure TNJWebSocket.Ping; begin if FSocket <> nil then asm (@FSocket).ping(function() {}); end; end; function TNJWebSocket.Protocol: string; begin if FSocket <> nil then Result := FSocket.protocol; end; function TNJWebSocket.URL:String; begin if FSocket <> nil then Result:=FSocket.url; end; function TNJWebSocket.SocketState: JWsReadyState; begin if FSocket <> nil then Result := JWsReadyState( integer( FSocket.readyState) ) else Result := rsClosed; end; function TNJWebSocket.Connected: boolean; begin Result := SocketState = rsOpen; end; procedure TNJWebSocket.Connect(URL: string); begin Connect(Url, []); end; procedure TNJWebSocket.Connect(URL: string; Protocols: Array of string); begin ClearLastError(); (* disconnect socket if already connected *) if connected then disconnect(); (* Allocate new socket *) var WebSocket = WebSocketAPI; asm (@self.FSocket) = new (@WebSocket)(@URL, @Protocols); end; // initialize standard socket events FSocket.on("open", procedure begin if assigned(OnOpen) then OnOpen(self); end); FSocket.on("error", procedure (error: variant) begin SetLastError("internal websocket error"); if assigned(OnError) then OnError(self, TJSErrorObject(error)); end); FSocket.on("message", procedure (message: variant) var ResData: TNJWebsocketMessage; begin if message.IsUint8Array then begin ResData.wiType := mtBinary; ResData.wiBuffer := JBuffer(message); end else begin ResData.wiType := mtText; ResData.wiText := message; end; if assigned(OnMessage) then OnMessage(self, ResData); end); Variant(FSocket).on("close", procedure (code: integer; reason: string) begin if assigned(OnClosed) then OnClosed(self, code, reason); end); end; procedure TNJWebSocket.Disconnect; begin ClearLastError(); if Connected then begin try FSocket.close(); finally FSocket := nil; end; end; end; procedure TNJWebSocket.Send(const Data: variant); begin FSocket.send(data); end; procedure TNJWebSocket.Send(const Text: string); begin FSocket.send(Text); end; procedure TNJWebSocket.Send(const Data: TStream); begin if Data <> nil then begin if Data.position < Data.Size then begin // Get bytes from stream var Bytes := Data.Read(Data.Size - Data.Position); // Convert to typed-array var TypedArray := TDataType.BytesToTypedArray(bytes); // Send as binary FSocket.Send(TypedArray); end; end; end; end. It might be a bit simplified, and it doesn't really follow the SMS component name convention, but it does what I need. And I figured I would share. -David
  11. 3 points
    jarto

    Sorting arrays and lists

    This is a tutorial for sorting arrays and lists in Smart Mobile Studio. String arrays and TStringList are pretty easy to sort. You only need to call Sort: var strarr: array of String; ... strarr.Sort; var strlist: TStringList; strlist:=TStringList.Create; ... strlist.Sort; But how do you sort arrays of records or arrays of classes? For example: TSortData = record Id: Integer; Data: String; end; You can't simply call Sort as your application does not know how you want to sort the records. You solve this by using a comparison function: function CompareSortData(Item1, Item2: TSortData): Integer; begin result:=CompareStr(Item1.Data, Item2.Data); end; . . . var objarr: array of TSortData; ... objarr.Sort(@CompareSortData); The comparison function should return a negative number if Item1 is smaller, positive number if Item1 is bigger and 0 if they are equal. In Smart Mobile Studio you can also use anonymous methods: var objarr: array of TSortData; ... objarr.Sort( function(Item1, Item2: TSortData): Integer begin result:=CompareStr(Item1.Data,Item2.Data); end); Comparison functions can also be used for TList and even for TStringList. For example, you may want to have greater control over how names are sorted (eliminating prefixes like von): function EliminatePrefix(Name: String): String; const Prefixes: array[0..1] of String = ['von ','af ']; begin result:=Name; for var a:=0 to High(Prefixes) do begin if pos(Prefixes[a],result)=1 then begin Delete(result,1,Length(Prefixes[a])); exit; end; end; end; . . . var StrList:=TStringList.Create; StrList.Add('Perry'); StrList.Add('Miller'); StrList.Add('von Essen'); StrList.Add('Stockton'); StrList.Add('af Trolle'); StrList.Sort( function(Name1,Name2: String): Integer begin result:=CompareStr(EliminatePrefix(Name1),EliminatePrefix(Name2)); end); for var a:=0 to StrList.Count-1 do WriteLn(StrList[a]);
  12. 3 points
    jarto

    Development updates

    A new update is available. This one contains a completely new Grid: TW3StringGrid. It's a completely new design, which was made to be very fast and able to handle lots of data. This is achieved through dynamic drawing: Only the visible grid lines are rendered. There is a new StringGrid-demo in the Featured Demos. It downloads a json file and populates the grid with data from it. RTL: New control: TW3StringGrid New featured demo for the StringGrid. New TW3CustomScrollControl, for developing scroll controls with scrollbars/indicators-support. New border types: btLightBorderTop, -Bottom, -Left, -Right, -Horiz and -Vert EventManager: Check TouchPreventDefault before trying to prevent extra artificial mouse events from touch events Improvements to TW3BlockBox and Modal dialogs: TW3BlockBox did not trigger a resize Dialogs did not get resized when window was resized or device flipped Make sure the dialogs are not too big
  13. 3 points
    jarto

    UI layout

    Yes, we need anchors and aligns. Now that I'm about to get TW3StringGrid finished, I'll have a look at Anchors and Align.
  14. 3 points
    DavidRM

    Chat Scroller - Need Some Tips

    I built this component to use in my Paintball Net game UI: unit UPBTInfoScroll; interface uses System.Types, System.Lists, System.Time, System.Colors, SmartCL.System, SmartCL.Theme, SmartCL.Components, SmartCL.Controls.Label, SmartCL.Scroll, SmartCL.Controls.ScrollBar, SmartCL.Css.Classes; type TPBTScrollItem = class(TW3CustomControl) protected FItemText: string; procedure InitializeItem; virtual; procedure SetItemText(aValue: string); public function CreationFlags: TW3CreationFlags; override; procedure InitializeObject; override; procedure UpdateDisplay; virtual; property ItemText: string read FItemText write SetItemText; end; TPBTScrollItemClass = class of TPBTScrollItem; TPBTScrollInfoItem = class(TPBTScrollItem) protected public procedure UpdateDisplay; override; property InfoText: string read ItemText write ItemText; end; TPBTScrollErrorItem = class(TPBTScrollInfoItem) end; TPBTChatType = (ctAnnounce, ctChat, ctSay, ctShout, ctWhisper, ctTell, ctPlan, ctTeamPlan, ctWizChat); TPBTScrollChatItem = class(TPBTScrollItem) private FChatType: TPBTChatType; FChatHeader: string; FChatText: string; FChatColor: TColor; public procedure UpdateDisplay; override; property ChatTYpe: TPBTChatType read FChatType; property ChatHeader: string read FChatHeader; property ChatText: string read FChatText; property ChatColor: TColor read FChatColor; end; TPBTScrollItems = class(TW3ScrollContent) private function GetItem(Index: Integer): TPBTScrollItem; function GetCount: integer; public function Add: TPBTScrollItem; overload; function Add(aScrollItemClass: TPBTScrollItemClass): TPBTScrollItem; overload; procedure Clear; virtual; procedure FinalizeObject; override; procedure Delete(aIndex: integer); published property Items[index: Integer]: TPBTScrollItem read GetItem; default; property Count: integer read GetCount; end; TPBTInfoScrollAddItemCallback = function: TPBTScrollItem; TPBTInfoScroll = class(TW3ScrollControl) private function GetItems: TPBTScrollItems; function GetScrollIsAtBottom: boolean; protected procedure InitializeObject; override; procedure Resize; override; function GetScrollContentClass: TW3ScrollContentClass; override; public function CreationFlags: TW3CreationFlags; override; procedure Clear; procedure ScrollToBottom; procedure RealignItems; procedure AddItem(aCallBack: TPBTInfoScrollAddItemCallback); procedure AddText(const aText: string); procedure AddChat(aChatType: TPBTChatType; const aChatHeader, aChatText: string; aChatColor: TColor); procedure AddInfo(const aInfoText: string); procedure AddError(const aErrorText: string); function MaxInfoWidth: integer; function MinInfoWidth: integer; published property Items: TPBTScrollItems read GetItems; property ScrollIsAtBottom: boolean read GetScrollIsAtBottom; end; implementation // TPBTScrollItem procedure TPBTScrollItem.InitializeItem; begin end; procedure TPBTScrollItem.SetItemText(aValue: string); begin if aValue <> FItemText then begin FItemText := aValue; UpdateDisplay; end; end; function TPBTScrollItem.CreationFlags: TW3CreationFlags; begin inherited; Include(Result, cfAllowSelection); end; procedure TPBTScrollItem.InitializeObject; begin inherited; SetContentSelectionMode(tsmText); end; procedure TPBTScrollItem.UpdateDisplay; begin InnerHTML := Format('<span style="user-select:text;-moz-user-select:text;-webkit-user-select:text;-ms-user-select:text;-khtml-user-select:text">%s</span>', [TString.EncodeTags(FItemText)]); end; // TPBTScrollInfoItem procedure TPBTScrollInfoItem.UpdateDisplay; begin inherited; end; // TPBTScrollErrorItem // TPBTScrollChatItem procedure TPBTScrollChatItem.UpdateDisplay; begin inherited; InnerHTML := Format('<span style="color:%s;user-select:text;-moz-user-select:text;-webkit-user-select:text;-ms-user-select:text;-khtml-user-select:text"><b>%s</b>: %s</span>', [ColorToWebStr(FChatColor), TString.EncodeTags(FChatHeader), TString.EncodeTags(FChatText)]); end; // TPBTScrollItems function TPBTScrollItems.GetItem(Index: Integer): TPBTScrollItem; begin Result := TPBTScrollItem(GetChildren[Index]); end; function TPBTScrollItems.GetCount: integer; begin Result := GetChildCount; end; function TPBTScrollItems.Add: TPBTScrollItem; begin Result := Add(TPBTScrollItem); end; function TPBTScrollItems.Add(aScrollItemClass: TPBTScrollItemClass): TPBTScrollItem; begin Result := aScrollItemClass.Create(Self); Result.InitializeItem; end; procedure TPBTScrollItems.Clear; begin // can't get rid of from [0]. Not sure why. while Count > 0 do GetChildren[Count - 1].Free; Height := 0; end; procedure TPBTScrollItems.FinalizeObject; begin Clear; inherited; end; procedure TPBTScrollItems.Delete(aIndex: integer); begin if aIndex < GetChildCount then GetChildren[aIndex].Free; end; // TPBTInfoScroll function TPBTInfoScroll.GetItems: TPBTScrollItems; begin Result := TPBTScrollItems(Content); end; function TPBTInfoScroll.GetScrollIsAtBottom: boolean; begin Result := (ScrollController.ContentTop <= -(Content.Height - (Height + 10))) end; procedure TPBTInfoScroll.InitializeObject; begin inherited; SetBarSize(CNT_SCROLLBAR_SIZE); ScrollBars := sbScrollBar; end; procedure TPBTInfoScroll.Resize; begin inherited; end; function TPBTInfoScroll.GetScrollContentClass: TW3ScrollContentClass; begin Result := TPBTScrollItems; end; function TPBTInfoScroll.CreationFlags: TW3CreationFlags; begin inherited; // Allow key-capture and selection include(result, cfKeyCapture); include(result, cfAllowSelection); end; procedure TPBTInfoScroll.Clear; begin Items.Clear; SetSize(ClientWidth, 0); ScrollController.Refresh; end; procedure TPBTInfoScroll.ScrollToBottom; begin ScrollController.ScrollTo(0, -(Content.Height - Height)); end; procedure TPBTInfoScroll.RealignItems; var ii: integer; begin ScrollController.Refresh; if Items.Count > 0 then begin Items.BeginUpdate; try Items[0].Top := 0; for ii := 1 to Items.Count - 1 do begin Items[ii].Top := Items[ii - 1].Top + Items[ii - 1].Height; end; finally Items.EndUpdate; end; end; end; procedure TPBTInfoScroll.AddItem(aCallBack: TPBTInfoScrollAddItemCallback); const margin = 5; MAX_ITEMS = 500; var lastItem, aItem: TPBTScrollItem; needRealign, wasAtBottom: boolean; begin Items.BeginUpdate; Content.BeginUpdate; try if Items.Count > 0 then lastItem := Items[Items.Count - 1]; needRealign := Items.Count > MAX_ITEMS; if needRealign then while Items.Count > (MAX_ITEMS div 3) * 2 do Items.Delete(0); if Content.Width <> (ClientWidth - GetBarSize) then Content.Width := (ClientWidth - GetBarSize); wasAtBottom := (ScrollController.ContentTop <= -(Content.Height - (Height + 10))); aItem := aCallBack; aItem.UpdateDisplay; if lastItem <> nil then aItem.Top := lastItem.Top + lastItem.Height else aItem.Top := 0; Content.Height := aItem.Top + aItem.Height + margin; if Content.Height <= Height then begin Content.Height := Height; wasAtBottom := True; end; finally Content.EndUpdate; Items.EndUpdate; end; if needRealign then RealignItems; ScrollController.Refresh; if wasAtBottom then ScrollToBottom; if needRealign then Invalidate; end; procedure TPBTInfoScroll.AddText(const aText: string); begin AddItem( lambda Result := Items.Add; Result.ItemText := aText; end ); end; procedure TPBTInfoScroll.AddChat(aChatType: TPBTChatType; const aChatHeader, aChatText: string; aChatColor: TColor); begin AddItem( lambda Result := Items.Add(TPBTScrollChatItem); TPBTScrollChatItem(Result).FChatType := aChatType; TPBTScrollChatItem(Result).FChatHeader := aChatHeader; TPBTScrollChatItem(Result).FChatText := aChatText; TPBTScrollChatItem(Result).FChatColor := aChatColor; end ); end; procedure TPBTInfoScroll.AddInfo(const aInfoText: string); begin AddItem( lambda Result := Items.Add(TPBTScrollInfoItem); TPBTScrollInfoItem(Result).InfoText := aInfoText; end ); end; procedure TPBTInfoScroll.AddError(const aErrorText: string); begin AddItem( lambda Result := Items.Add(TPBTScrollErrorItem); TPBTScrollErrorItem(Result).InfoText := aErrorText; end ); end; function TPBTInfoScroll.MaxInfoWidth: integer; var aMax: string; begin while aMax.Length < 70 do aMax += 'XXXXXXXXXX'; Result := MeasureText(aMax).tmWidth + GetBarSize; end; function TPBTInfoScroll.MinInfoWidth: integer; begin Result := MaxInfoWidth div 2; end; end. My goal was to have it act like a scrolling chat. New items are added to the bottom, pushing up the items already there. By default scrolling "sticks" to the bottom of the view area, unless the player has manually scrolled it back to review something or whatever. Overall, it works. What I'm looking for are missed opportunities and maybe some solutions to a couple issues: The biggest issue is the build up of items in the scroll. Right now, I have it check for a maximum size, then prune it down to half that size. I would *prefer* if it could be indefinitely large. The solutions I've seen for that have relied on items being the same height. That constraint doesn't work for me. Resizing isn't exactly snappy/smooth. Just seems like there's probably a way to make that better. Also, as a sorta sub-goal, I wanted to share what I had done. SMS needs a lot more sharing going on. 😃 So...hit me. Tell me how I'm doing it wrong and/or could do it better. Thanks! -David
  15. 3 points
    jarto

    Update embedded Chromium

    Hi guys, I've been a busy for a while working on updating the Embedded Chrome we use in the IDE. The one we've used so far has been from Henri Gourvest. Unfortunately it's not been updated for 3 years, but there's a new fork by Salvador Diaz Fau, which is actively updated: https://github.com/salvadordf/CEF4Delphi
  16. 2 points
    As you all know, almost all classes in Smart Mobile Studio are named TW3Something. For example: TW3Button, TW3Panel etc. The decision to do that was done during the first stages of development of Smart Mobile Studio. However, there's no technical reason why the "W3" has to be there. So basically, we could rename all the components and classes to be more compatible with Delphi and making code sharing and porting easier. I wrote a program yesterday evening that analyzed and renamed all the classes and records that start with TW3 in the RTL. There are 849 of them to be precise. The app also renamed themes and the IDE code, so that I was able to compile a new IDE and make my first app with TForm and TButton etc. It all worked nicely, except that the chart component is causing a bit of trouble. It's using TPanel and a couple of other classes that would cause conflicts. I'd be able to add remapping functions to the IDE so that you could load an old project and all the TW3Whatever-classes would be converted to TWhatever. But if you have your own components using names like TPanel, TButton, TList, TStringList, they would cause trouble. Question is: Do you have them and how much of code like this do you have? Any thoughts?
  17. 2 points
    jarto

    UI layout

    ' Working on this now. Align in the RTL is at pretty good shape already.
  18. 2 points
    jarto

    RoadMap 2019

    Well, I believe that open sourcing is not a silver bullet that will magically bring new resources to the project. If there are people who are interested in participating, I'll be more than happy to actually offer them contract work. Before I joined this project, SMS was in an eternal "coming soon" -stage. A new update was just around the corner but days turned into weeks and weeks to months and years. I didn't like that as a customer. So when I've been running this development, I've not wanted to make promises that I can't keep. Before this my family owned and ran a very succesfull software company that developed the best and most used School Administration software in Finland. We did constant development making new features. We used constant evolution. I've been doing that same thing with Smart Mobile Studio and we really have seen a lot of development and improvements. And we will see constant and good development in the future too. When it comes to a roadmap, I've noticed that I'm really bad at making one. A software project is a moving target. A lot of features are being worked on. In the end, it's not clear when a feature will be finished and delivered. That's because priorities do change and sometimes (for example the new Grid) technology throws us a hard challenge that takes a very long time to solve. At the moment our top priorities are: Improve documentation, tutorials, demos. Update internal browser to a newer version of Chromium. Upgrade compiler to use a newer version of DWScript. Improve IDE (Designer, Wizards) On top of that we keep a keen eye on the needs and requests of our users and add new features/components to the RTL based on that. @Czar Thanks for the tip about the events related bug in the IDE. I was able to reproduce it. There are restrictions on what the IDE can do at the moment. And some restrictions actually depend on what's the best way of developing for the browser. For example, it's a lot better to have fonts and font sizes in CSS instead of setting them in the Object Inspector. @IElite Let me know what you want to see regarding mobile devices.
  19. 2 points
    jarto

    Font size and style in buttons

    I'd like to open up a little discussion about font sizes. Specifically, the font size in buttons, which to my eyes looks too big and bulky compared to all other text: Suggestion 1: Decrease font size: Suggestion 2: Decrease font size, remove bold and text shadow, black text: Which one do you guys like most?
  20. 2 points
    warleyalex

    vendor prefix

    The CSS browser prefixes that you can use are: Android: -webkit- Chrome: -webkit- Firefox: -moz- Internet Explorer: -ms- iOS: -webkit- Opera: -o- Safari: -webkit- Please, fix the smartCL.System --> unitFCSSToken := 'Moz'; ---> I think should be lowecase w3_setStyle(FParticles.Handle, BrowserAPI.PrefixDef('transform'), 'scale(0.5,0.5)'); w3_setStyle(FParticles.Handle, BrowserAPI.PrefixDef('transform-origin'), 'left top');
  21. 2 points
    jarto

    TW3StringGrid is available

    Sure, I can add a Button column type too. Also OnCellChanged will be added in the next update. I've also been working on sorting. As the data is dynamically drawn, it's actually surprisingly easy to add. The trickiest is just to decide how the sorting interface should be done (properties etc.) so that I don't unnecessarily restrict future features. For example, in many Delphi grids there are quite detailed possibilities to control sorting.
  22. 2 points
    lynkfs

    visual processing

    See previous post : any type of no-code / lo-code solution requires a visual ide, not only for the visual components like buttons etc but also for the non-visual process components. Such an ide allows for both visual and non-visual components to be connected according to the principles of FBP. See previous post and link here. To see how this would pan out, I made this proof of concept no-code / lo-code web-based ide. (Proof of concept not being a full fledged finished product, but at least gives some insights) To diminish the strain on the brain a bit, the layout is based on the Smart ide, including the shameless reuse of its icons. Some highlights : - drag and drop placement of components rather than point and select - the usual component move and resize facilities but now including Delphi-type align options - drag and drop fbp connections between components using jsPlumb A quick rundown : 1) drag a button on the design pane 2) select 'Database' from the top tab-control, and drag both a grid and mySQL component on the design pane. Note the property pane is updated in realtime 3) (optionally click-select any dragged component to resize them. Select both grid and button, right-click on either for alignment options) 4) switch to the No-code tab and connect the button to the mySQL component, and the mySQL component to the grid 5) right-click the mySQL component and provide details (user-id, server, passwords etc). Not shown 6) switch to the Preview tab and click the button This project compiles to 63 KB (0.06 M. Not bad Project code here
  23. 2 points
    Having a cursory view on this library, what you probably want to end up with is a pascal variable called 'client', which you can use outside asm blocks. This will allow you to issue calls like client.getEventList() etc. to do that you can do something like this var client : variant := new JObject; asm @client = new JSONRpcClient({ 'url': '//user-api.simplybook.me', 'headers': { 'X-Company-Login': 'ACME', 'X-Token': token }, 'onerror': function (error) { alert(error); } }); end; var EventList: variant := client.getEventList(-params-); rule of thumb: the creation of js objects via the 'new' constructor need to be kept in an asm block
  24. 2 points
    lynkfs

    Calendar Control

    Easiest way : procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components var CalenderBox : TW3Panel := TW3Panel.Create(self); CalenderBox.SetBounds(10,10,252,266); CalenderBox.handle.id := 'CalenderBox'; var myCalendar : variant := new JObject; asm @myCalendar = new dhtmlXCalendarObject("CalenderBox"); end; myCalendar.disableDays("week", [7]); myCalendar.setDateFormat("%Y-%m-%d"); myCalendar.setHolidays("2019-03-19"); var yesterday : float := Now()-1; myCalendar.setInsensitiveRange(null, DateToStr(yesterday)); myCalendar.attachEvent("onClick", procedure(date:variant) begin writeln(myCalendar.getFormatedDate('%d-%m-%Y')); end); myCalendar.show(); end; make sure you include the necessary dhtmlx files in your index.html (Project Manager pane, right-click, add Template and select default.html from the Templates directory. This creates a new unit Custom Template. Then add the 2 lines below) . I used the cdn version (which contains the whole suite) <link rel="stylesheet" href="http://cdn.dhtmlx.com/edge/dhtmlx.css" type="text/css"> <script src="http://cdn.dhtmlx.com/edge/dhtmlx.js" type="text/javascript"></script> (alternatively you can import these files in code too)
  25. 2 points
    lynkfs

    node ground zero

    I had the idea firmly rooted in my mind that Node is for server-side deployment only. This happens to be not the case, it is apparently feasible to use Node packages in the browser ! Talking about packages, the largest store of Node packages is NPM, which stores some 500,000 packages or more. (350,000 as per jan 2017) Not all of those are suitable for use in the browser, but a lot of them are. The ones which don't work client-side are the ones that require internet access, graphics or files, so this is not a work-around to be able to access databases on the client without going through a server, or enabling to write files client-side. Probably a good thing. One of the ways to use an existing node package (or a self-written one) client-side is Browserify. This product basically takes a packages and iterates through all its dependencies to other packages and collates them in a single bundle. Which can then be used client-side. Browserify also has written client-side versions of some of Node's core modules, which are automatically included in a bundle when necessary. HTTP and HTTPS for instance. That still doesn't make it possible to set up a http server within the browser, but other http-functions will work : the first program in this thread (see top of this thread) was a vanilla js node program which accesses www.random.org (a site which generates random integers) and prints out the value to the console. in plain vanilla javascript : var https = require('https'); //The url is: 'https://www.random.org/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new' var options = { host: 'www.random.org', path: '/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new' }; callback = function(response) { var str = ''; //another chunk of data has been received, so append it to `str` response.on('data', function (chunk) { str += chunk; }); //the whole response has been received, so we just print it out here response.on('end', function () { console.log(str); }); } https.request(options, callback).end(); Browserifying this package and accessing it from the browser works fine, and can be an alternative to issuing xmlhttprequests Another example using Node's event mechanism // require the core node events module var EventEmitter = require('events').EventEmitter //create a new event emitter var emitter = new EventEmitter // set up a listener for the event emitter.on('test', function (message) { console.log(message) }) // emit an event emitter.emit('test', 'this is an event test') works fine as well, an alternative to add event listeners etc It wouldn't be too difficult to Smarten this up either. I'm having a look at the other 500,000 packages. Daunting but interesting.
×