[dali_2.3.23] Merge branch 'devel/master'
[platform/core/uifw/dali-demo.git] / examples / builder / examples.cpp
1 /*
2  * Copyright (c) 2023 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 //------------------------------------------------------------------------------
19 //
20 //
21 //------------------------------------------------------------------------------
22
23 #include <dali-toolkit/dali-toolkit.h>
24 #include <dali-toolkit/devel-api/builder/builder.h>
25 #include <dali-toolkit/devel-api/builder/json-parser.h>
26 #include <dali-toolkit/devel-api/builder/tree-node.h>
27 #include <dali-toolkit/devel-api/controls/navigation-view/navigation-view.h>
28 #include <dali-toolkit/devel-api/controls/popup/popup.h>
29 #include <dali/dali.h>
30
31 #include <dirent.h>
32 #include <stdio.h>
33 #include <fstream>
34 #include <iostream>
35 #include <map>
36 #include <sstream>
37 #include <streambuf>
38 #include <string>
39
40 #include <cstring>
41 #include <ctime>
42 #include "sys/stat.h"
43
44 #include <dali/devel-api/adaptor-framework/file-loader.h>
45 #include <dali/integration-api/debug.h>
46 #include "shared/view.h"
47
48 #define TOKEN_STRING(x) #x
49
50 using namespace Dali;
51 using namespace Dali::Toolkit;
52
53 namespace
54 {
55 const char* BACKGROUND_IMAGE("");
56 const char* TOOLBAR_IMAGE(DEMO_IMAGE_DIR "top-bar.png");
57 const char* EDIT_IMAGE(DEMO_IMAGE_DIR "icon-change.png");
58 const char* EDIT_IMAGE_SELECTED(DEMO_IMAGE_DIR "icon-change-selected.png");
59
60 std::string USER_DIRECTORY;
61
62 std::string JSON_BROKEN(
63   "                                      \
64 {                                                              \
65   'stage':                                                     \
66   [                                                            \
67     {                                                          \
68       'type':'TextLabel',                                      \
69       'size': [50,50,1],                                       \
70       'parentOrigin': 'CENTER',                                \
71       'text':'COULD NOT LOAD JSON FILE'                        \
72     }                                                          \
73   ]                                                            \
74 }                                                              \
75 ");
76
77 std::string ReplaceQuotes(const std::string& single_quoted)
78 {
79   std::string s(single_quoted);
80
81   // wrong as no embedded quote but had regex link problems
82   std::replace(s.begin(), s.end(), '\'', '"');
83
84   return s;
85 }
86
87 std::string GetFileContents(const std::string& fn)
88 {
89   std::streampos     bufferSize = 0;
90   Dali::Vector<char> fileBuffer;
91   if(!Dali::FileLoader::ReadFile(fn, bufferSize, fileBuffer, FileLoader::FileType::BINARY))
92   {
93     return std::string();
94   }
95
96   return std::string(&fileBuffer[0], bufferSize);
97 };
98
99 typedef std::vector<std::string> FileList;
100
101 void DirectoryFileList(const std::string& directory, FileList& files)
102 {
103   DIR*           d;
104   struct dirent* dir;
105   d = opendir(directory.c_str());
106   if(d)
107   {
108     while((dir = readdir(d)) != NULL)
109     {
110       if(dir->d_type == DT_REG)
111       {
112         files.push_back(directory + std::string(dir->d_name));
113       }
114     }
115
116     closedir(d);
117   }
118 }
119
120 void DirectoryFilesByType(const std::string& dir, const std::string& fileType /* ie "json" */, FileList& files)
121 {
122   typedef FileList           Collection;
123   typedef FileList::iterator Iter;
124
125   Collection allFiles;
126   DirectoryFileList(dir, allFiles);
127
128   for(Iter iter = allFiles.begin(); iter != allFiles.end(); ++iter)
129   {
130     size_t pos = (*iter).rfind('.');
131     if(pos != std::string::npos)
132     {
133       if((*iter).substr(pos + 1) == fileType)
134       {
135         files.push_back((*iter));
136       }
137     }
138   }
139 }
140
141 const std::string ShortName(const std::string& name)
142 {
143   size_t pos = name.rfind('/');
144
145   if(pos != std::string::npos)
146   {
147     pos++;
148     return name.substr(pos);
149   }
150   else
151   {
152     return name;
153   }
154 }
155
156 //------------------------------------------------------------------------------
157 //
158 //
159 //
160 //------------------------------------------------------------------------------
161 class FileWatcher
162 {
163 public:
164   FileWatcher(void);
165   ~FileWatcher(void);
166   explicit FileWatcher(const std::string& fn)
167   : mLastTime(0),
168     mstringPath{}
169   {
170     SetFilename(fn);
171   };
172
173   void        SetFilename(const std::string& fn);
174   std::string GetFilename() const;
175
176   bool        FileHasChanged(void);
177   std::string GetFileContents(void) const
178   {
179     return ::GetFileContents(mstringPath);
180   };
181
182 private:
183   // compiler does
184   // FileWatcher(const FileWatcher&);
185   // FileWatcher &operator=(const FileWatcher &);
186
187   std::time_t mLastTime;
188   std::string mstringPath;
189 };
190
191 FileWatcher::FileWatcher(void)
192 : mLastTime(0),
193   mstringPath{}
194 {
195 }
196
197 bool FileWatcher::FileHasChanged(void)
198 {
199   struct stat buf;
200
201   if(0 != stat(mstringPath.c_str(), &buf))
202   {
203     return false;
204   }
205   else
206   {
207     const bool result = buf.st_mtime > mLastTime;
208     mLastTime         = std::time_t(buf.st_mtime);
209     return result;
210   }
211
212   return false;
213 }
214
215 FileWatcher::~FileWatcher()
216 {
217 }
218
219 void FileWatcher::SetFilename(const std::string& fn)
220 {
221   mstringPath = fn;
222   FileHasChanged(); // update last time
223 }
224
225 std::string FileWatcher::GetFilename(void) const
226 {
227   return mstringPath;
228 }
229
230 } // namespace
231
232 //------------------------------------------------------------------------------
233 //
234 //
235 //
236 //------------------------------------------------------------------------------
237 class ExampleApp : public ConnectionTracker, public Toolkit::ItemFactory
238 {
239 public:
240   ExampleApp(Application& app)
241   : mApp(app)
242   {
243     app.InitSignal().Connect(this, &ExampleApp::Create);
244   }
245
246   ~ExampleApp()
247   {
248   }
249
250 public:
251   void SetTitle(const std::string& title)
252   {
253     if(!mTitleActor)
254     {
255       mTitleActor = DemoHelper::CreateToolBarLabel("");
256       // Add title to the tool bar.
257       mToolBar.AddControl(mTitleActor, DemoHelper::DEFAULT_VIEW_STYLE.mToolBarTitlePercentage, Alignment::HORIZONTAL_CENTER);
258     }
259
260     mTitleActor.SetProperty(TextLabel::Property::TEXT, title);
261   }
262
263   bool OnBackButtonPressed(Toolkit::Button button)
264   {
265     OnQuitOrBack();
266     return true;
267   }
268
269   void SetUpItemView()
270   {
271     Window window = mApp.GetWindow();
272
273     mTapDetector = TapGestureDetector::New();
274     mTapDetector.DetectedSignal().Connect(this, &ExampleApp::OnTap);
275
276     mFiles.clear();
277
278     mItemView = ItemView::New(*this);
279
280     mItemView.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
281     mItemView.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
282     mLayout = DefaultItemLayout::New(DefaultItemLayout::LIST);
283
284     mLayout->SetItemSize(Vector3(window.GetSize().GetWidth(), 50, 1));
285
286     mItemView.AddLayout(*mLayout);
287
288     mItemView.SetProperty(Actor::Property::KEYBOARD_FOCUSABLE, true);
289
290     mFiles.clear();
291     FileList files;
292
293     if(USER_DIRECTORY.size())
294     {
295       DirectoryFilesByType(USER_DIRECTORY, "json", files);
296     }
297     else
298     {
299       DirectoryFilesByType(DEMO_SCRIPT_DIR, "json", files);
300     }
301
302     std::sort(files.begin(), files.end());
303
304     for(FileList::iterator iter = files.begin(); iter != files.end(); ++iter)
305     {
306       JsonParser parser = JsonParser::New();
307
308       std::string data(GetFileContents(*iter));
309
310       parser.Parse(data);
311
312       if(parser.ParseError())
313       {
314         std::cout << "Parser Error:" << *iter << std::endl;
315         std::cout << parser.GetErrorLineNumber() << "(" << parser.GetErrorColumn() << "):" << parser.GetErrorDescription() << std::endl;
316         exit(1);
317       }
318
319       if(parser.GetRoot())
320       {
321         if(const TreeNode* node = parser.GetRoot()->Find("stage"))
322         {
323           // only those with a stage section
324           if(node->Size())
325           {
326             mFiles.push_back(*iter);
327           }
328           else
329           {
330             std::cout << "Ignored file (stage has no nodes?):" << *iter << std::endl;
331           }
332         }
333         else
334         {
335           std::cout << "Ignored file (no stage section):" << *iter << std::endl;
336         }
337       }
338     }
339
340     // Activate the layout
341     Vector3 size(window.GetSize());
342     mItemView.ActivateLayout(0, size, 0.0f /*immediate*/);
343   }
344
345   void OnTap(Actor actor, const TapGesture& tap)
346   {
347     ItemId id = mItemView.GetItemId(actor);
348
349     LoadFromFileList(id);
350   }
351
352   Actor MenuItem(const std::string& text)
353   {
354     TextLabel label = TextLabel::New(ShortName(text));
355     label.SetStyleName("BuilderLabel");
356     label.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::WIDTH);
357
358     // Hook up tap detector
359     mTapDetector.Attach(label);
360
361     return label;
362   }
363
364   bool OnTimer()
365   {
366     if(mFileWatcher.FileHasChanged())
367     {
368       LoadFromFile(mFileWatcher.GetFilename());
369     }
370
371     return true;
372   }
373
374   void ReloadJsonFile(const std::string& filename, Builder& builder, Layer& layer)
375   {
376     Window window = mApp.GetWindow();
377
378     builder = Builder::New();
379     builder.QuitSignal().Connect(this, &ExampleApp::OnQuitOrBack);
380
381     Property::Map defaultDirs;
382     defaultDirs[TOKEN_STRING(DEMO_IMAGE_DIR)]  = DEMO_IMAGE_DIR;
383     defaultDirs[TOKEN_STRING(DEMO_MODEL_DIR)]  = DEMO_MODEL_DIR;
384     defaultDirs[TOKEN_STRING(DEMO_SCRIPT_DIR)] = DEMO_SCRIPT_DIR;
385
386     builder.AddConstants(defaultDirs);
387
388     // render tasks may have been setup last load so remove them
389     RenderTaskList taskList = window.GetRenderTaskList();
390     if(taskList.GetTaskCount() > 1)
391     {
392       typedef std::vector<RenderTask> Collection;
393       typedef Collection::iterator    ColIter;
394       Collection                      tasks;
395
396       for(unsigned int i = 1; i < taskList.GetTaskCount(); ++i)
397       {
398         tasks.push_back(taskList.GetTask(i));
399       }
400
401       for(ColIter iter = tasks.begin(); iter != tasks.end(); ++iter)
402       {
403         taskList.RemoveTask(*iter);
404       }
405
406       RenderTask defaultTask = taskList.GetTask(0);
407       defaultTask.SetSourceActor(window.GetRootLayer());
408       defaultTask.SetFrameBuffer(FrameBuffer());
409     }
410
411     unsigned int numChildren = layer.GetChildCount();
412
413     for(unsigned int i = 0; i < numChildren; ++i)
414     {
415       layer.Remove(layer.GetChildAt(0));
416     }
417
418     std::string data(GetFileContents(filename));
419
420     try
421     {
422       builder.LoadFromString(data);
423     }
424     catch(...)
425     {
426       builder.LoadFromString(ReplaceQuotes(JSON_BROKEN));
427     }
428
429     builder.AddActors(layer);
430   }
431
432   void LoadFromFileList(size_t index)
433   {
434     if(index < mFiles.size())
435     {
436       const std::string& name = mFiles[index];
437       mFileWatcher.SetFilename(name);
438       LoadFromFile(name);
439     }
440   }
441
442   void LoadFromFile(const std::string& name)
443   {
444     ReloadJsonFile(name, mBuilder, mBuilderLayer);
445
446     mBuilderLayer.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::BOTTOM_CENTER);
447     mBuilderLayer.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::BOTTOM_CENTER);
448     Dali::Vector3 size = mApp.GetWindow().GetRootLayer().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
449     size.y -= DemoHelper::DEFAULT_VIEW_STYLE.mToolBarHeight;
450     mBuilderLayer.SetProperty(Actor::Property::SIZE, size);
451
452     mNavigationView.Push(mBuilderLayer);
453   }
454
455   void Create(Application& app)
456   {
457     Window window = app.GetWindow();
458
459     window.KeyEventSignal().Connect(this, &ExampleApp::OnKeyEvent);
460
461     Layer contents = DemoHelper::CreateView(app,
462                                             mView,
463                                             mToolBar,
464                                             BACKGROUND_IMAGE,
465                                             TOOLBAR_IMAGE,
466                                             "");
467
468     SetTitle("Select Example");
469
470     mBuilderLayer = Layer::New();
471
472     // Create an edit mode button. (left of toolbar)
473     Toolkit::PushButton backButton = Toolkit::PushButton::New();
474     backButton.SetProperty(Toolkit::Button::Property::UNSELECTED_BACKGROUND_VISUAL, EDIT_IMAGE);
475     backButton.SetProperty(Toolkit::Button::Property::SELECTED_BACKGROUND_VISUAL, EDIT_IMAGE_SELECTED);
476     backButton.ClickedSignal().Connect(this, &ExampleApp::OnBackButtonPressed);
477     backButton.SetProperty(Actor::Property::LEAVE_REQUIRED, true);
478     mToolBar.AddControl(backButton, DemoHelper::DEFAULT_VIEW_STYLE.mToolBarButtonPercentage, Toolkit::Alignment::HORIZONTAL_LEFT, DemoHelper::DEFAULT_MODE_SWITCH_PADDING);
479
480     mNavigationView = Toolkit::NavigationView::New();
481     mNavigationView.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
482     mNavigationView.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
483
484     window.Add(mNavigationView);
485
486     // Set up the background gradient.
487     Property::Array stopOffsets;
488     stopOffsets.PushBack(0.0f);
489     stopOffsets.PushBack(1.0f);
490     Property::Array stopColors;
491     stopColors.PushBack(Color::WHITE);
492     stopColors.PushBack(Vector4(0.45f, 0.70f, 0.80f, 1.0f)); // Medium bright, pastel blue
493     const float percentageWindowHeight = window.GetSize().GetHeight() * 0.6f;
494
495     mNavigationView.SetProperty(Toolkit::Control::Property::BACKGROUND, Dali::Property::Map().Add(Toolkit::Visual::Property::TYPE, Dali::Toolkit::Visual::GRADIENT).Add(Toolkit::GradientVisual::Property::STOP_OFFSET, stopOffsets).Add(Toolkit::GradientVisual::Property::STOP_COLOR, stopColors).Add(Toolkit::GradientVisual::Property::START_POSITION, Vector2(0.0f, -percentageWindowHeight)).Add(Toolkit::GradientVisual::Property::END_POSITION, Vector2(0.0f, percentageWindowHeight)).Add(Toolkit::GradientVisual::Property::UNITS, Toolkit::GradientVisual::Units::USER_SPACE));
496
497     SetUpItemView();
498     mNavigationView.Push(mItemView);
499
500     mTimer = Timer::New(500); // ms
501     mTimer.TickSignal().Connect(this, &ExampleApp::OnTimer);
502     mTimer.Start();
503
504   } // Create(app)
505
506   virtual unsigned int GetNumberOfItems()
507   {
508     return mFiles.size();
509   }
510
511   virtual Actor NewItem(unsigned int itemId)
512   {
513     DALI_ASSERT_DEBUG(itemId < mFiles.size());
514     return MenuItem(ShortName(mFiles[itemId]));
515   }
516
517   /**
518    * Main key event handler
519    */
520   void OnKeyEvent(const KeyEvent& event)
521   {
522     if(event.GetState() == KeyEvent::DOWN)
523     {
524       if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
525       {
526         OnQuitOrBack();
527       }
528     }
529   }
530
531   /**
532    * Event handler when Builder wants to quit (we only want to close the shown json unless we're at the top-level)
533    */
534   void OnQuitOrBack()
535   {
536     if(mItemView.GetProperty<bool>(Actor::Property::CONNECTED_TO_SCENE))
537     {
538       mApp.Quit();
539     }
540     else
541     {
542       mNavigationView.Pop();
543     }
544   }
545
546 private:
547   Application& mApp;
548
549   ItemLayoutPtr           mLayout;
550   ItemView                mItemView;
551   Toolkit::NavigationView mNavigationView;
552
553   Toolkit::Control mView;
554
555   Toolkit::ToolBar mToolBar;
556   TextLabel        mTitleActor;
557
558   Layer mBuilderLayer;
559
560   TapGestureDetector mTapDetector;
561
562   // builder
563   Builder mBuilder;
564
565   FileList mFiles;
566
567   FileWatcher mFileWatcher;
568   Timer       mTimer;
569 };
570
571 //------------------------------------------------------------------------------
572 //
573 //
574 //
575 //------------------------------------------------------------------------------
576 int DALI_EXPORT_API main(int argc, char** argv)
577 {
578   if(argc > 2)
579   {
580     if(strcmp(argv[1], "-f") == 0)
581     {
582       USER_DIRECTORY = argv[2];
583     }
584   }
585
586   Application app = Application::New(&argc, &argv, DEMO_THEME_PATH);
587
588   ExampleApp dali_app(app);
589
590   app.MainLoop();
591
592   return 0;
593 }