Jump to content

Leaderboard


Popular Content

Showing content with the highest reputation since 12/18/2018 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. 7 points
    Updated 5/2019 to add: Now online here: Paintball Net --- Back in the mid-90's, my brother created what he called an "action MUD". He called it "Paintball Net". PBN used ANSI text terminal commands to create a combined roll-and-scroll and animated text experience. ! (exclamation points) were trees, _ (underscores) were grass, ^ (up carets) were mountains, and more. Your "avatar" was a "Y" (uppercase y) and enemies were * (asterisks). You used MUD-like commands to move around the world and target your paintball gun and buy and sell and more. I wish I had a screenshot of what the first version looked like, but you'll just have to imagine. 😃 He asked me to create the GUI for the game. That was 1996, and I had just purchased Delphi 2 and wrapped up my first "learn Delphi" project. This sounded like a good next step, and "PBTerm" was born. I *do* have screenshots of that. Oh, yeah. Love those 16-color Windows graphics. 😃 During the years it was online, the game evolved and upgraded to look more like this: We were never going to win any awards, but we had a very devoted player community. 😃 Paintball Net was online from the summer of 1996 through the summer of 2000, when we took it offline to focus on other projects. The game was never huge, but we had thousands of players come through over that period. And since then, every year at least a few of the players have reached out and asked/begged/pleaded/demanded if we were going to put it back online. There really wasn't much chance of the original version going back online. The original server, written in ANSI C for Linux had proven rather fragile, and was a big part of why we took the game offline. It was taking hours every day just to keep it up and running, in addition to time spent managing the community of players. On top of that, a hard drive incident in 2008 had cost me the source code of a number of core third-party/modified components for PBTerm. This past summer, though, I realized I might be able to make the game live again using Smart Mobile Studio. I would do a straight port of the ANSI C server to Smart Pascal using Node.JS and WebSocket, and I would rebuild the PBTerm client as a browser-based client. I'm not going to say it was *easy*, but it has been a lot of fun. ANSI C converts to Pascal without a lot of painful gyrations. And Node.JS seems a LOT more flexible, stable, and powerful than the TCP sockets approach we were using before. Also, game development is a lot easier when you already own all the graphic and audio resources. 😃 This is the server "in action"... Recreating the client has been more complicated. 20+ years of Delphi habits had to be adapted to the new reality of SMS and a browser-based UI. But that's coming together too. It might be obvious, but I'm not targeting this game at mobile. Paintball Net needed a mouse and keyboard in 1996, it's gonna need a mouse and keyboard in 2018. Also, I've made as few modifications to the gameplay as possible. I really wanted to bring back the original as much as I could. Today I got the handful of sound effects integrated, which was easier than I expected. I'm planning to start testing soon. I just need to line up a server to use and find a few volunteers. I'm excited. This would never have happened without Smart Mobile Studio. So I figured I would share. Merry Xmas! -David
  3. 5 points
    jarto

    UI layout

  4. 5 points
    warleyalex

    async / await keywords

    look this following procedure: procedure main; begin Sleep(5000); console.log('A'); Sleep(3000); console.log('B'); end; when we run this code, it will immediately output 'A' and 'B' - JS the asyncronous nature. ...but I want our program to log 'A' after 5 seconds then log 'B' after 3 seconds... that why we, we need the async / await native keywords in SMS. In a "synchronous" manner. Await and Async are just syntactic sugar for working with promises. It would be cool if we can declare a method like: procedure main; async; The compiler can check whether the encompassing function has async() in then and emit expectd JS, for instance: (async function(){ })(); Yeah, we need to to wrap it inside an async function and add "await" before the method "Sleep" to work in synchrounous manner.ther await keyword native in SMS would be nice. await Sleep(5000); I found out an ugly workaround for the "Immediately Invoked Function Expression (IIFE) - async function" to work at SMS. { global functions } procedure async(fn: Variant = nil); external '(async function(){' ; procedure &end; external '})'; function await(promise: Variant): JPromise; external 'await '; function Sleep(ms: Integer): JPromise; begin Result := JPromise.Create( procedure(resolve,reject : TCallBack) begin window.setTimeout( procedure() begin console.log('Done waiting'); resolve(ms); end, ms); end); procedure main; begin async; await( Sleep(5000) ); console.log('A after 5s'); await (Sleep(3000)); console.log('B after 3s'); &end; end; end; the compiler will emit expected JS at least: function main() { (async function(){(null); await (Sleep$1(5000)); console.log("A after 5s"); await (Sleep$1(3000)); console.log("B after 3s"); })(); }; but it would super awesome if we could // define procedure as async function procedure main: async; procedure main; begin await Sleep(5000); console.log('A after 5s'); await Sleep(3000); console.log('B after 3s'); end;
  5. 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.
  6. 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.
  7. 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)
  8. 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
  9. 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.
  10. 4 points
    lynkfs

    scrolling big numbers

    Got challenged the other day by trying to scroll large numbers of rows in a listbox. Browsers are very good at scrolling, but when row numbers become large and the scroll context becomes complex, scroll behaviour deteriorates. A simple un-optimised scrolling div is, rule of thumb, able to scroll comfortably up to a couple of hundred rows. Depending on browser and complexity. In the native framework I tweaked that a bit by setting all rows outside the visible viewport to 'display:none'. That extends comfortable scrolling somewhat, say to numbers in the low thousands. A better way is to completely separate display from content. Meaning that the content needs to be held in some kind of memory structure, not in visual components, and that scrolling is redefined to re-using a small set of visual rows confined to the scroll-window. A structure to make that happen would be a <div> like component (TW3Panel) on a form with NativeScrolling set to true. Set up 2 child-panels on this component, where the first child-panel has dimensions equal to the maximum scrollable area, and a second childpanel dimensioned equal to this parent. The first child-panel is the scroller, which will always be completely empty. The second child-panel contains the visible rows, which will be refreshed on scroll-events. To keep this panel always in view, set the position attribute to '-webkit-sticky'. This demo shows that Chrome can readily handle 500,000 rows, even on mobile. FF a bit less. The good thing is that the number of rows doesn't really matter, scrolling behaviour stays the same regardless. Would be interesting to see if performance gets even better by pushing scrolling to the GPU, pretty sure Chrome does that by default. Infinite scrolling will be easy to implement using this structure too. Code procedure TForm1.W3Button1Click(Sender: TObject); begin //data var column1 := TVariant.CreateArray; for var i := 0 to 500000 do begin column1.push(inttostr(i)); end; var column2 := TVariant.CreateArray; for var i := 1 to 500001 do begin column2.push(inttostr(i)); end; //column2.sort(); //sorting works too //scrolling setup var rowHeight := 30; W3Panel.NativeScrolling := true; //Parent panel (in visual designer : width 408, height 266) var Panel1 : TW3Panel := TW3Panel.Create(W3Panel); //scroller (always empty!) Panel1.SetBounds (0,0,380,column1.length*rowHeight); //but with large height Panel1.BorderRadius := 0; //set up viewport var Panel2 : TW3Panel := TW3Panel.Create(W3Panel); //viewport, only the visual rows Panel2.SetBounds(0,0,380,262); //dimensions same as grid-parent Panel2.BorderRadius := 0; Panel2.handle.style.position := '-webkit-sticky'; Panel2.handle.style.position := '-moz-sticky'; Panel2.handle.style.position := '-ms-sticky'; Panel2.handle.style.position := '-o-sticky'; Panel2.handle.style.position := 'sticky'; Panel2.handle.style.top := '0px'; //set up columns Var CPanel1 : TW3Panel := TW3Panel.Create(Panel2); //column 1 CPanel1.SetBounds(-2,-2,200,264); CPanel1.BorderRadius := 0; Var CPanel2 : TW3Panel := TW3Panel.Create(Panel2); //column 2 CPanel2.SetBounds(200,-2,200,264); CPanel2.BorderRadius := 0; //initial fill viewport prior to first onscroll event for var j := 0 to 8 do begin //only first couple of rows var x : TW3Panel := TW3Panel.Create(CPanel1); //column 1 x.SetBounds(-2,j*rowHeight-2,200,rowHeight); x.BorderRadius := 0; x.innerHTML := column1[j]; x.onclick := procedure(sender:TObject) begin showmessage((sender as TW3Panel).innerHTML); end; // var y : TW3Panel := TW3Panel.Create(CPanel2); //column 2 y.SetBounds(-2,j*rowHeight-2,190,rowHeight); y.BorderRadius := 0; y.innerHTML := column2[j]; y.onclick := procedure(sender:TObject) begin showmessage((sender as TW3Panel).innerHTML); end; end; //fill viewport while scrolling var c1 := CPanel1.handle.children; var c2 := CPanel2.handle.children; W3Panel.onscroll := procedure(sender:tobject) begin var d : integer := trunc(W3Panel.handle.scrollTop/rowHeight); for var k := 0 to 8 do begin c1[k].innerHTML := column1[d+k]; c2[k].innerHTML := column2[d+k]; end; //fall back : ide chrome browser and iOS don't handle 'sticky' very well, //in that case set viewport position manually if Panel2.handle.style.position <> 'sticky' then if not w3_getIsSafari then Panel2.top := W3Panel.handle.scrollTop; end; end; infinity scroll : add to end of W3Panel.onscroll procedure //infinity scroll : if W3Panel.handle.scrollHeight - W3Panel.handle.scrollTop = W3Panel.handle.clientHeight then W3Panel.handle.scrollTop := 0;
  11. 4 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
  12. 4 points
    jarto

    Development updates

    New update available: RTL: New controls: TW3SpinButton, TW3ArrowUpButton, TW3ArrowDownButton, TW3ArrowLeftButton and TW3ArrowRightButton. Add 1px margin to TW3ButtonBorder to prevent the border from being clipped. Bug fixes to Tween.Effects IDE: Bug fixes and changes to renaming of forms: Renaming in Project Manager will not change form class any more. Form class name can be renamed in Object Inspector. Improvements to Project Statistics: Only count Total time when Smart Mobile Studio IDE has focus. Only Count Design time when changes are made in Form Designer. Add clear button. Component Palette: Added new controls New icons for many existing controls
  13. 4 points
    lynkfs

    Quantum computing with Smart

    Is it possible to do a bit of quantum computing using SMS ? The answer looks to be yes, using IBM's quantum computer resources which are partly made available for developers Basics first : IBM has released it's SDK to program these quantum computers using a visual composer, Python, a proprietary assembler QASM format and some run and execute utilities. There is however a second way of using its quantum resources, using the entry-points of is Q-experience REST server. Most if not all of the visual composer functions can be done by ajax-calls ! To demonstrate : the first thing is to acquire an api-token. (Create an account on https://quantumexperience.ng.bluemix.net/qx/experience. Log in and navigate to the Composer. Username > My Account, and then Advanced on the upper right. Then generate API Token. Api Tokens are personal and are not supposed to be shared.) The next thing is to generate a session token (access token). This can be done in code. This screenshot shows a couple of these REST server calls and its results : The first button (get access) generates a session token The second button queries which quantum computers are available, how many qubits can be used, if it is online and what type of system it is. Current results show 3 quantum systems - 2 real ones and 1 simulator The third button (current Temp) gives some processor info of the selected system. In this case the current temperature in Kelvin : the Melbourne processor is cooled to just above the absolute minimum. Next : the next button to implement will be to submit some code to one of the available processors for processing. Below is the project code for these REST calls unit Form1; interface uses System.Types, System.Types.Convert, System.Objects, System.Time, SmartCL.System, SmartCL.Time, SmartCL.Graphics, SmartCL.Components, SmartCL.FileUtils, SmartCL.Forms, SmartCL.Fonts, SmartCL.Theme, SmartCL.Borders, SmartCL.Application, SmartCL.Net.Http, ECMA.Json, SmartCL.Controls.Button, SmartCL.Controls.Label, HTMLTableElement, SmartCL.Controls.ScrollBox, SmartCL.Controls.Panel, SmartCL.Controls.ComboBox; type TForm1 = class(TW3Form) procedure W3Button3Click(Sender: TObject); procedure W3Button2Click(Sender: TObject); procedure W3Button1Click(Sender: TObject); private {$I 'Form1:intf'} protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; DBRows : integer := 0; FHttp : TW3HttpRequest; smscursor: variant; procedure GetAccessToken(Sender: TW3HttpRequest); procedure ListAvailableSystems(Sender: TW3HttpRequest); procedure GetTempKelvin(Sender: TW3HttpRequest); AccessToken : string; end; implementation { TForm1 } procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components end; procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} end; { Button1 } procedure TForm1.W3Button1Click(Sender: TObject); begin //Get Access Token var FHttp := TW3HttpRequest.Create; FHttp.OnDataReady := GetAccessToken; FHttp.open("POST",'https://quantumexperience.ng.bluemix.net/api/users/loginWithToken'); FHttp.RequestHeaders.Add("Content-type","application/json"); var api : variant := new JObject; api.apiToken := TString.encodeURIComponent('a59d26ebf........1d6d6d4a28c430b5336aba'); FHttp.send(json.stringify(api)); end; procedure TForm1.GetAccessToken(Sender: TW3HttpRequest); begin smscursor := JSON.parse(Sender.ResponseText); AccessToken := smscursor.id; W3Label1.Caption:= 'Access Token : ' + AccessToken; end; { Button2 } procedure TForm1.W3Button2Click(Sender: TObject); begin //List available Q systems var FHttp := TW3HttpRequest.Create; FHttp.OnDataReady := ListAvailableSystems; FHttp.open("GET",'https://quantumexperience.ng.bluemix.net/api/Backends?access_token=' + AccessToken); FHttp.RequestHeaders.Add("Content-type","application/json"); FHttp.send; end; procedure TForm1.ListAvailableSystems(Sender: TW3HttpRequest); begin var W3TableElement1 : TW3TableElement := TW3TableElement.Create(W3Panel1); //add 5 columns to the grid W3TableElement1.AddColumn('Name',200); //title, width W3TableElement1.AddColumn('Description',200); W3TableElement1.AddColumn('Qubits',50); W3TableElement1.AddColumn('System type',150); W3TableElement1.AddColumn('Status',50); smscursor := JSON.parse(Sender.ResponseText); W3ComboBox1.Clear; for var i := 0 to smscursor.length -1 do begin W3TableElement1.AddCell(i+1,1,smscursor[i].name); W3TableElement1.AddCell(i+1,2,smscursor[i].description); W3TableElement1.AddCell(i+1,3,smscursor[i].nQubits); if smscursor[i].simulator = true then W3TableElement1.AddCell(i+1,4,'quantum simulator') else W3TableElement1.AddCell(i+1,4,'quantum system'); W3TableElement1.AddCell(i+1,5,smscursor[i].status); W3ComboBox1.add(smscursor[i].name); end; W3Panel1.NativeScrolling := true; W3ComboBox1.OnChanged := procedure (Sender: TObject) begin writeln(W3ComboBox1.Items[W3ComboBox1.SelectedIndex]); end; end; { Button3 } procedure TForm1.W3Button3Click(Sender: TObject); begin //Get Temp var FHttp := TW3HttpRequest.Create; FHttp.OnDataReady := GetTempKelvin; //'https://quantumexperience.ng.bluemix.net/api/Backends/NAME/parameters?access_token=ACCESS-TOKEN'; FHttp.open("GET",'https://quantumexperience.ng.bluemix.net/api/Backends/' + W3ComboBox1.Items[W3ComboBox1.SelectedIndex] + '/parameters?access_token=' + AccessToken); FHttp.RequestHeaders.Add("Content-type","application/json"); FHttp.send; end; procedure TForm1.GetTempKelvin(Sender: TW3HttpRequest); begin If Sender.ResponseText = '{}' then begin W3Label3.Caption := 'Simulators are not cooled to (close to) absolute minimum'; end else begin smscursor := JSON.parse(Sender.ResponseText); if smscursor.fridgeParameters.Temperature.value = '' then W3Label3.Caption := 'Processor does not support Temp reading' else W3Label3.Caption:= 'Current Temp : ' + smscursor.fridgeParameters.Temperature.value + ' Kelvin'; end; end; procedure TForm1.Resize; begin inherited; end; initialization Forms.RegisterForm({$I %FILE%}, TForm1); end. In the end hopefully it will be possible to recreate the classic Battleships game using quantum computing, as described here
  14. 4 points
    warleyalex

    ecma.promise

    The unit ECMA.Promise is broken. I use this definition Using Promises in SMS unit uPromises; interface uses W3C.Console, W3C.DOM, W3C.XMLHttpRequest; type TVariantDynArray = array of Variant; JDeferred = class; TJPromiseCallback = procedure(Value: Variant); TJDeferredObject_fn = function(d: TJPromiseCallback): Variant; TJDeferredObject = procedure(resolve: TJPromiseCallback; reject: TJPromiseCallback); TJPromiseCallback_fn = function(Value: Variant): Variant; TJDeferredEventHandler = function(event: Variant): Variant; JPromise = class external 'Promise' constructor Create(fn: TJDeferredObject_fn { = nil}); overload; constructor Create(resolve: TJDeferredObject_fn; reject: TJDeferredObject_fn); overload; constructor Create(fn: TJDeferredObject); overload; function always(alwaysCallbacks: TVariantDynArray): JPromise; function done(doneCallbacks: TVariantDynArray): JPromise; overload; function done(doneCallbacks: Variant): JPromise; overload; function fail(failCallbacks: TVariantDynArray): JPromise; function progress(progressCallbacks: TVariantDynArray): JPromise; function state(): string; function &then(doneCallbacks: Variant; failCallbacks: Variant{ = undefined}; progressCallbacks: Variant { = undefined}): JPromise; external 'then'; function &then(onFulfilled: TJPromiseCallback_fn = nil): JPromise; overload; external 'then'; function &then(onFulfilled: TJPromiseCallback_fn; onRejected: TJPromiseCallback_fn): JPromise; overload; external 'then'; function &then(onFulfilled: TJPromiseCallback; onRejected: TJPromiseCallback): JPromise; overload; external 'then'; function catch(rejecTJPromiseCallback: Variant = nil): JPromise; overload; function catch(rejecTJPromiseCallback: TJPromiseCallback_fn): JPromise; overload; class function promise(target: Variant): JPromise; end; type JDeferred = class external 'Promise'(JPromise) function notify(args: TVariantDynArray): JDeferred; function notifyWith(context: Variant; args: TVariantDynArray): JDeferred; function reject(args: TVariantDynArray): JDeferred; overload; function reject(args: Variant): JDeferred; overload; function reject(args: TJDeferredEventHandler): JDeferred; overload; function rejectWith(context: Variant; args: TVariantDynArray): JDeferred; function resolve(args: TVariantDynArray): JDeferred; overload; function resolve(value: Variant = nil): JPromise; overload; function resolveWith(context: Variant; args: TVariantDynArray): JDeferred; function all(iterable: TVariantDynArray): JPromise; overload; function race(iterable: TVariantDynArray): JPromise; end; { global external functions } function Promise : JDeferred; external 'Promise' property; function queue: JPromise; external 'Promise.resolve()'; function Error(message: variant): variant; external 'Error'; function document: variant; external "document" property; function window : Variant; external 'window' property; function &typeof(obj:variant): variant; overload; external "typeof"; function wait(ms: Integer): JPromise; function getURI(url: string): Variant; function getFile(url: string): variant; //function myRequire(url: string): Variant; implementation function wait(ms: Integer): JPromise; function setTimeout(ahandler : TJPromiseCallback; aTimeout : Integer): Integer; external 'window.setTimeout'; begin result := JPromise.Create( procedure (resolve, reject: TJPromiseCallback) begin setTimeout(resolve, ms); end); end; function getURI(url: string): Variant; var request: JXMLHttpRequest; procedure p(resolve: TJPromiseCallback; reject: TJPromiseCallback); // Standard XHR to load an image procedure doOnLoad; begin // This is called even on 404 etc // so check the status if (request.status = 200) then begin // If successful, resolve the promise by passing back the request response resolve(request.response); end else begin // Otherwise reject with the status text // which will hopefully be a meaningful error reject(Error('File didn''t load successfully; error code: ' + request.statusText)); end; end; procedure doOnError; begin // Also deal with the case when the entire request fails to begin with // This is probably a network error, so reject the promise with an appropriate message reject(Error('There was a network error.')); end; Begin request := JXMLHttpRequest.Create; request.open('GET', url); // When the request loads, check whether it was successful request.addEventListener('load', @doOnLoad); // Handle network errors request.addEventListener('abort', @doOnError); // Send the request request.send(); End; begin // Create new promise with the Promise() constructor; // This has as its argument a function // with two parameters, resolve and reject Result := JPromise.Create(@p); end; function getFile(url: string): variant; begin // Create new promise with the Promise() constructor; // This has as its argument a function // with two parameters, resolve and reject Result := JPromise.create( procedure(resolve: TJPromiseCallback; reject: TJPromiseCallback) // Standard XHR to load an image var request: JXMLHttpRequest; begin request := new JXMLHttpRequest(); request.open('GET', url); // When the request loads, check whether it was successful request.onload := lambda begin // This is called even on 404 etc // so check the status if (request.status = 200) then begin // If successful, resolve the promise by passing back the request response resolve(request.response); end else begin // Otherwise reject with the status text // which will hopefully be a meaningful error reject(Error("File didn't load successfully; error code: " + request.statusText)); end; end; end; // Handle network errors request.onerror := lambda // Also deal with the case when the entire request fails to begin with // This is probably a network error, so reject the promise with an appropriate message reject(Error('There was a network error.')); end; // Send the request request.send(); end); end; /* function myRequire( url: string): Variant; function ev(win: Variant; arr: array of Variant): Variant; external 'eval.apply'; var ajax: JXMLHttpRequest; function onReady(event: JEvent): Variant; var script: Variant; begin script := ajax.response; // ?? ajax.responseText; if (ajax.readyState = 4) then begin case( (ajax.status)) of 200: begin //eval.apply( window, [script] ); ev( window, [script] ); console.log('script loaded: '+ url); end else console.log('ERROR: script not loaded: '+ url); end; end; end; begin ajax := JXMLHttpRequest.Create; ajax.open( 'GET', url, false ); // <-- the 'false' makes it synchronous ajax.onreadystatechange := @onReady; ajax.send(null); end;*/ end.
  15. 3 points
    jarto

    Smart Mobile Studio 3.0.2 is released!

    Smart Mobile Studio 3.0.2 is released. This version contains all the fixes and improvements from the development-channel. There are lots of bug fixes and improvements to the IDE thanks to all the help from our users here. The Smart Mobile Studio team wants to with everybody a Merry Christmas and Happy New Year. Release notes: https://smartmobilestudio.com/2018/12/21/smart-mobile-studio-3-0-2-released/
  16. 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.
  17. 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
  18. 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]);
  19. 3 points
    lynkfs

    visual processing

    A while ago I posted some ideas and a demo on the subject of 'nocode' or 'locode' development. The idea was to do a domain modelling exercise, and extract or generate an application from that with no or minimal coding required. The demo put a couple of the proposed models through its paces. After that post I put the subject back in the incubator for a bit. Sort of recently I realised that having an rtl with visual components and an ide with a form painter is a good thing to have, but only covers part of what is necessary if we need to cover process logic, or business rules, without having to resort to coding. Various computing packages (datamining, visual analytics etc) model business logic by providing specialised components, which can be strung together and parametrised. I tried this approach out (using KNIME) with the following (nonsensical) example process : "from the NorthWind database get all companies, and from the FishFacts database extract all species, collate only those entries from both datasources where the name starts with the letter 'L' and list them. Also produce a pie-chart of all fish species showing their length in cm." This (nonsense) process translates to the node structure above. Nodes typically can be selected from a list of available nodes and right-clicking gives parametrisation forms, f.i. the MYSQL connector asks for a host, database name, credentials etc. Stringing these components together is a simple process in itself, and the output produces something like Not too bad. I've coded a couple of these type of components in Smart, which works really well. For the technical architecture I've based these on the principles of FBP (flow based programming) where every node is a webworker, and the connectors between these nodes/webworkers are defined by channel-objects. Data transfer between these nodes/channels is done by messaging. See post here. It would be nice to extend the rtl with these types of components, including a process painter
  20. 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
  21. 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.
  22. 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
  23. 3 points
    jarto

    Development updates

    New update is available: Bug fixes and improvements to the IDE: BringToFront and SendToBack will now affect component creation order Opening files from Project Search Path is fixed
  24. 3 points
    jarto

    Endpoints & Resources ?

    Well, you should always take what Jon says or writes with a grain of salt.
  25. 3 points
    DavidRM

    NodeJS file utils and native app

    Here's my example: Files := TW3NodeStorageDevice.Create(nil); Files.Mount(nil, procedure (Sender: TW3StorageDevice; Success: boolean) begin if Success then Writeln(PBNRS_FILE_SYSTEM_MOUNTED) else Writeln(Format(PBNRS_ERROR_MOUNTING_FILE_SYSTEM, [Files.LastError])); end ); And reading a file: Files.Load(PBNConfig.HelpFile, procedure (Sender: TW3StorageDevice; Filename: string; Data: TStream; Success: boolean) begin if Success then begin ... end else ... end
×