From 78b351b753600b525b22d33479b8be2651ffc63c Mon Sep 17 00:00:00 2001 From: kpl Date: Mon, 23 Mar 2020 10:48:49 -0400 Subject: [PATCH] cwUi* : Initial implementation. --- cwUi.cpp | 466 ++++++++++++++++++++++++++++++++++++ cwUi.h | 89 +++++++ cwUiDecls.h | 12 + cwUiTest.cpp | 184 +++++++++++++++ cwUiTest.h | 7 + html/uiTest/css/ui.css | 9 + html/uiTest/index.html | 22 ++ html/uiTest/js/ui.js | 518 +++++++++++++++++++++++++++++++++++++++++ html/uiTest/ui.cfg | 15 ++ 9 files changed, 1322 insertions(+) create mode 100644 cwUi.cpp create mode 100644 cwUi.h create mode 100644 cwUiDecls.h create mode 100644 cwUiTest.cpp create mode 100644 cwUiTest.h create mode 100644 html/uiTest/css/ui.css create mode 100644 html/uiTest/index.html create mode 100644 html/uiTest/js/ui.js create mode 100644 html/uiTest/ui.cfg diff --git a/cwUi.cpp b/cwUi.cpp new file mode 100644 index 0000000..dace083 --- /dev/null +++ b/cwUi.cpp @@ -0,0 +1,466 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwThread.h" +#include "cwWebSock.h" +#include "cwWebSockSvr.h" +#include "cwUi.h" +#include "cwText.h" +#include "cwNumericConvert.h" + +namespace cw +{ + namespace ui + { + typedef struct ele_str + { + struct ele_str* parent; // pointer to parent ele - or nullptr if this ele is attached to the root ui ele + unsigned uuId; // UI unique id - automatically generated and unique among all elements that are part of this ui_t object. + unsigned appId; // application assigned id - application assigned id + char* jsId; // javascript id + } ele_t; + + typedef struct ui_str + { + websockSrv::handle_t wssH; + unsigned eleAllocN; + unsigned eleN; + ele_t** eleA; + uiCallback_t cbFunc; + void* cbArg; + } ui_t; + + ui_t* _handleToPtr( handle_t h ) + { return handleToPtr(h); } + + rc_t _destroy( ui_t* p ) + { + rc_t rc = kOkRC; + + if( p->wssH.isValid() ) + if((rc = websockSrv::destroy(p->wssH)) != kOkRC ) + return rc; + + for(unsigned i=0; ieleN; ++i) + { + mem::release(p->eleA[i]->jsId); + mem::release(p->eleA[i]); + } + + mem::release(p->eleA); + mem::release(p); + + return rc; + } + + ele_t* _createEle( ui_t* p, ele_t* parent, unsigned appId, const char* jsId ) + { + ele_t* e = mem::allocZ(); + e->parent = parent; + e->uuId = p->eleN; + e->appId = appId; + e->jsId = mem::duplStr(jsId); + + if( p->eleN == p->eleAllocN ) + { + p->eleAllocN += 100; + p->eleA = mem::resizeZ(p->eleA,p->eleAllocN); + } + + p->eleA[ p->eleN ] = e; + p->eleN += 1; + + return e; + } + + ele_t* _uuIdToEle( ui_t* p, unsigned uuId, bool errorFl=true ) + { + if( uuId >= p->eleN ) + { + cwLogError(kInvalidIdRC,"The element uuid:%i is not valid.",uuId); + return nullptr; + } + + return p->eleA[ uuId ]; + } + + + rc_t _websockSend( ui_t* p, const char* msg ) + { + return websock::send( websockSrv::websockHandle( p->wssH ), kUiProtocolId, msg, strlen(msg) ); + } + + const char* _findEleJsId( ui_t* p, unsigned uuId ) + { + for(unsigned i=0; ieleN; ++i) + if( p->eleA[i]->uuId == uuId ) + return p->eleA[i]->jsId; + + return nullptr; + } + + // Print the attribute data value. + template< typename T0 > + unsigned format_attribute_data( char* buf, unsigned n, T0 t0 ) + { + return toText(buf,n,t0); + } + + // Override format_attribute_data() for char. string data so that strings are wrapped in quotes. + template<> + unsigned format_attribute_data( char* buf, unsigned n, const char* t ) + { + unsigned i = 0; + i += toText(buf+i, n-i, "\"" ); + i += toText(buf+i, n-i, t ); + i += toText(buf+i, n-i, "\"" ); + return i; + } + + // terminating condition for format_attributes() + unsigned format_attributes(char* buf, unsigned n, unsigned i) + { return i; } + + template + unsigned format_attributes(char* buf, unsigned n, unsigned i, T0 t0, T1 t1, ARGS&&... args) + { + i += toText(buf+i, n-i, ",\"" ); + i += toText(buf+i, n-i, t0 ); + i += toText(buf+i, n-i, "\":" ); + i += format_attribute_data(buf+i, n-i, t1 ); + + return format_attributes(buf,n,i,std::forward(args)...); + } + + template< typename... ARGS> + rc_t _createOneEle( ui_t* p, unsigned& uuIdRef, const char* eleTypeStr, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, ARGS&&... args ) + { + // { op:create, parent:my_parent_id, value:{ button:{ jsId:my_jsId, appId:appId, uuId:uuId, class:clas, title:'my title' } } + rc_t rc = kOkRC; + const char* parentJsId = ""; + ele_t* newEle = nullptr; + ele_t* parentEle = nullptr; + const unsigned bufN = 1024; + char buf[ bufN ]; + + uuIdRef = kInvalidId; + + // + if( parentUuId != kInvalidId ) + { + if(( parentEle = _uuIdToEle(p, parentUuId )) == nullptr ) + return cwLogError( kInvalidArgRC, "Unable to locate the parent element (id:%i).", parentUuId ); + + parentJsId = parentEle->jsId; + } + + newEle = _createEle( p, parentEle, appId, jsId ); + + unsigned i = snprintf( buf, bufN, "{ \"op\":\"create\", \"parent\":\"%s\", \"value\":{ \"%s\":{ \"jsId\":\"%s\", \"appId\":%i, \"uuId\":%i, \"class\":\"%s\", \"title\":\"%s\" ", parentJsId, eleTypeStr, jsId, appId, newEle->uuId, clas, title ); + + i = format_attributes(buf, bufN, i, std::forward(args)...); + + toText(buf+i, bufN-i, "}}}"); + + printf("%s\n",buf); + + rc = _websockSend( p, buf ); + + uuIdRef = newEle->uuId; + + + return rc; + } + + + ele_t* _parse_value_msg( ui_t* p, value_t& valueRef, const char* msg ) + { + rc_t rc = kOkRC; + char argType = 0; + ele_t* ele = nullptr; + unsigned eleUuId = kInvalidId; + + valueRef.tid = kInvalidTId; + + if( msg == nullptr ) + { + cwLogWarning("Empty message received from UI."); + return nullptr; + } + + // locate the colon prior to the value + const char* s = strchr(msg,':'); + + if( s == nullptr || sscanf(msg, "value %i %c ",&eleUuId,&argType) != 2 ) + { + cwLogError(kSyntaxErrorRC,"Invalid message from UI: %s.", msg ); + goto errLabel; + } + + // advance s past the colon + s += 1; + + // parse the argument + switch( argType ) + { + case 'b': + if((rc = string_to_number(s,valueRef.u.b)) == kOkRC ) + valueRef.tid = kBoolTId; + break; + + case 'i': + if((rc = string_to_number(s,valueRef.u.i)) == kOkRC ) + valueRef.tid = kIntTId; + break; + + case 'u': + if((rc = string_to_number(s,valueRef.u.u)) == kOkRC ) + valueRef.tid = kUIntTId; + break; + + case 'f': + if((rc = string_to_number(s,valueRef.u.f)) == kOkRC ) + valueRef.tid = kFloatTId; + break; + + case 'd': + if((rc = string_to_number(s,valueRef.u.d)) == kOkRC ) + valueRef.tid = kDoubleTId; + break; + + case 's': + if((valueRef.u.s = nextNonWhiteChar(s)) == nullptr ) + valueRef.tid = kStringTId; + break; + + default: + rc = cwLogError(kInvalidIdRC,"Unknown value type '%c' in message from UI.", argType ); + goto errLabel; + } + + // locate the element record + if((ele = _uuIdToEle( p, eleUuId )) == nullptr ) + { + cwLogError(kInvalidIdRC,"UI message elment not found."); + goto errLabel; + } + + errLabel: + + return ele; + } + + void _websockCb( void* cbArg, unsigned protocolId, unsigned connectionId, websock::msgTypeId_t msg_type, const void* msg, unsigned byteN ) + { + ui_t* p = (ui_t*)cbArg; + opId_t opId = kInvalidOpId; + unsigned eleUuId = kInvalidId; + unsigned eleAppId = kInvalidId; + unsigned parentEleAppId = kInvalidId; + value_t value; + + switch( msg_type ) + { + case websock::kConnectTId: + opId = kConnectOpId; + break; + + case websock::kDisconnectTId: + opId = kDisconnectOpId; + break; + + case websock::kMessageTId: + { + ele_t* ele; + + if( textCompare((const char*)msg,"init",strlen("init")) == 0 ) + { + opId = kInitOpId; + } + else + { + opId = kValueOpId; + if((ele = _parse_value_msg(p, value, (const char*)msg )) == nullptr ) + cwLogError(kOpFailRC,"UI Value message parse failed."); + else + { + eleUuId = ele->uuId; + eleAppId = ele->appId; + parentEleAppId = ele->parent == nullptr ? kInvalidId : ele->parent->appId; + } + } + } + break; + + default: + cwLogError(kInvalidOpRC,"Unknown websock message type:%i.", msg_type ); + return; + } + + if( p->cbFunc != nullptr ) + p->cbFunc( p->cbArg, connectionId, opId, parentEleAppId, eleUuId, eleAppId, &value ); + } + } +} + +cw::rc_t cw::ui::createUi( + handle_t& h, + unsigned port, + uiCallback_t cbFunc, + void* cbArg, + const char* physRootDir, + const char* dfltPageFn, + unsigned websockTimeOutMs, + unsigned rcvBufByteN, + unsigned xmtBufByteN ) +{ + rc_t rc = kOkRC; + + websock::protocol_t protocolA[] = + { + { "http", kHttpProtocolId, 0, 0 }, + { "ui_protocol", kUiProtocolId, rcvBufByteN, xmtBufByteN } + }; + + unsigned protocolN = sizeof(protocolA)/sizeof(protocolA[0]); + + if((rc = destroyUi(h)) != kOkRC ) + return rc; + + ui_t* p = mem::allocZ(); + + if((rc = websockSrv::create(p->wssH, _websockCb, p, physRootDir, dfltPageFn, port, protocolA, protocolN, websockTimeOutMs )) != kOkRC ) + { + cwLogError(rc,"Internal websock server creation failed."); + goto errLabel; + } + + p->eleAllocN = 100; + p->eleA = mem::allocZ( p->eleAllocN ); + p->eleN = 0; + p->cbFunc = cbFunc; + p->cbArg = cbArg; + + h.set(p); + + errLabel: + + if( rc != kOkRC ) + { + _destroy(p); + } + + return rc; +} + +cw::rc_t cw::ui::start( handle_t h ) +{ + rc_t rc = kOkRC; + ui_t* p = _handleToPtr(h); + + if((rc = websockSrv::start(p->wssH)) != kOkRC ) + rc = cwLogError(rc,"Internal websock server start failed."); + + return rc; +} + +cw::rc_t cw::ui::stop( handle_t h ) +{ + rc_t rc = kOkRC; + ui_t* p = _handleToPtr(h); + + if((rc = websockSrv::pause(p->wssH)) != kOkRC ) + rc = cwLogError(rc,"Internal websock server stop failed."); + + return rc; +} + +cw::rc_t cw::ui::destroyUi( handle_t& h ) +{ + rc_t rc = kOkRC; + if( !h.isValid() ) + return rc; + + ui_t* p = _handleToPtr(h); + if((rc = _destroy(p)) != kOkRC ) + return rc; + + h.clear(); + + return rc; +} + +unsigned cw::ui::findElementAppId( handle_t h, unsigned parentUuId, const char* jsId ) +{ + ui_t* p = _handleToPtr(h); + + for(unsigned i=0; ieleN; ++i) + if( p->eleA[i]->parent->uuId==parentUuId && strcmp(p->eleA[i]->jsId,jsId) == 0 ) + return p->eleA[i]->appId; + return kInvalidId; +} + +unsigned cw::ui::findElementUuId( handle_t h, unsigned parentUuId, const char* jsId ) +{ + ui_t* p = _handleToPtr(h); + + for(unsigned i=0; ieleN; ++i) + if( p->eleA[i]->parent->uuId==parentUuId && strcmp(p->eleA[i]->jsId,jsId) == 0 ) + return p->eleA[i]->uuId; + return kInvalidId; +} + +unsigned cw::ui::findElementUuId( handle_t h, unsigned parentUuId, unsigned appId ) +{ + ui_t* p = _handleToPtr(h); + + for(unsigned i=0; ieleN; ++i) + if( p->eleA[i]->parent->uuId==parentUuId && p->eleA[i]->appId == appId ) + return p->eleA[i]->uuId; + return kInvalidId; +} + +const char* cw::ui::findElementJsId( handle_t h, unsigned uuId ) +{ + ui_t* p = _handleToPtr(h); + return _findEleJsId(p,uuId); +} + + +cw::rc_t cw::ui::createDiv( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "div", parentUuId, jsId, appId, clas, title ); } + +cw::rc_t cw::ui::createTitle( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "option", parentUuId, jsId, appId, clas, title ); } + +cw::rc_t cw::ui::createButton( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "button", parentUuId, jsId, appId, clas, title ); } + +cw::rc_t cw::ui::createCheck( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, bool value ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "check", parentUuId, jsId, appId, clas, title, "value", value ); } + +cw::rc_t cw::ui::createSelect( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "select", parentUuId, jsId, appId, clas, title ); } + +cw::rc_t cw::ui::createOption( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "option", parentUuId, jsId, appId, clas, title ); } + +cw::rc_t cw::ui::createString( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, const char* value ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "string", parentUuId, jsId, appId, clas, title, "value", value ); } + +cw::rc_t cw::ui::createNumber( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, double value, double minValue, double maxValue, double stepValue, unsigned decpl ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "number", parentUuId, jsId, appId, clas, title, "value", value, "min", minValue, "max", maxValue, "step", stepValue, "decpl", decpl ); } + +cw::rc_t cw::ui::createProgress( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, double value, double minValue, double maxValue ) +{ return _createOneEle( _handleToPtr(h), uuIdRef, "progress", parentUuId, jsId, appId, clas, title, "value", value, "min", minValue, "max", maxValue ); } + +cw::rc_t cw::ui::createText( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ) +{ + rc_t rc= kOkRC; + return rc; +} + + + + diff --git a/cwUi.h b/cwUi.h new file mode 100644 index 0000000..119fa72 --- /dev/null +++ b/cwUi.h @@ -0,0 +1,89 @@ +#ifndef cwUI_H +#define cwUI_H + +#include "cwUiDecls.h" + +namespace cw +{ + namespace ui + { + typedef handle handle_t; + + enum + { + kHttpProtocolId = 1, + kUiProtocolId = 2 + }; + + typedef enum + { + kInvalidOpId, + kConnectOpId, + kInitOpId, + kValueOpId, + kDisconnectOpId + } opId_t; + + typedef enum + { + kInvalidTId, + kBoolTId, + kIntTId, + kUIntTId, + kFloatTId, + kDoubleTId, + kStringTId + } dtypeId_t; + + typedef struct + { + dtypeId_t tid; + union + { + bool b; + int i; + unsigned u; + float f; + double d; + const char* s; + } u; + } value_t; + + typedef rc_t (*uiCallback_t)( void* cbArg, unsigned connId, opId_t opId, unsigned parentAppId, unsigned uuId, unsigned appId, const value_t* value ); + + rc_t createUi( handle_t& h, + unsigned port, + uiCallback_t cbFunc, + void* cbArg, + const char* physRootDir, + const char* dfltPageFn = "index.html", + unsigned websockTimeOutMs = 50, + unsigned rcvBufByteN = 1024, + unsigned xmtBufByteN = 1024); + + rc_t destroyUi( handle_t& h ); + + rc_t start( handle_t h ); + rc_t stop( handle_t h ); + + unsigned findElementAppId( handle_t h, unsigned parentUuId, const char* jsId ); + unsigned findElementUuId( handle_t h, unsigned parentUuId, const char* jsId ); + unsigned findElementUuId( handle_t h, unsigned parentUuId, unsigned appId ); + const char* findElementJsId( handle_t h, unsigned uuId ); + + rc_t createDiv( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ); + rc_t createTitle( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ); + rc_t createButton( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ); + rc_t createCheck( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, bool value ); + rc_t createSelect( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ); + rc_t createOption( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ); + rc_t createString( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, const char* value ); + rc_t createNumber( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, double value, double minValue, double maxValue, double stepValue, unsigned decPl ); + rc_t createProgress( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title, double value, double minValue, double maxValue ); + rc_t createText( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* jsId, unsigned appId, const char* clas, const char* title ); + + } +} + + +#endif diff --git a/cwUiDecls.h b/cwUiDecls.h new file mode 100644 index 0000000..f173de4 --- /dev/null +++ b/cwUiDecls.h @@ -0,0 +1,12 @@ +#ifndef cwUiDecls_H +#define cwUiDecls_H + +namespace cw +{ + namespace ui + { + + } +} + +#endif diff --git a/cwUiTest.cpp b/cwUiTest.cpp new file mode 100644 index 0000000..a6856d3 --- /dev/null +++ b/cwUiTest.cpp @@ -0,0 +1,184 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwUi.h" +#include "cwUiTest.h" + + +namespace cw +{ + namespace ui + { + typedef struct ui_test_str + { + handle_t uiH; + } ui_test_t; + + enum + { + kDivId, + kBtnId, + kCheckId, + kSelectId, + kOption0Id, + kOption1Id, + kOption2Id, + kOption3Id, + kStringId, + kNumberId, + kProgressId + }; + + rc_t _uiTestCreateUi( ui_test_t* p, unsigned connId ) + { + rc_t rc = kOkRC; + unsigned uuid = kInvalidId; + unsigned selUuId = kInvalidId; + unsigned divUuId = kInvalidId; + + if((rc = createDiv( p->uiH, divUuId, kInvalidId, "myDivId", kDivId, "divClass", "My Panel" )) != kOkRC ) + goto errLabel; + + if((rc = createButton( p->uiH, uuid, divUuId, "myBtnId", kBtnId, "btnClass", "Push Me" )) != kOkRC ) + goto errLabel; + + if((rc = createCheck( p->uiH, uuid, divUuId, "myCheckId", kCheckId, "checkClass", "Check Me", true )) != kOkRC ) + goto errLabel; + + if((rc = createSelect( p->uiH, selUuId, divUuId, "mySelId", kSelectId, "selClass", "Select" )) != kOkRC ) + goto errLabel; + + if((rc = createOption( p->uiH, uuid, selUuId, "myOpt0Id", kOption0Id, "optClass", "Option 0" )) != kOkRC ) + goto errLabel; + + if((rc = createOption( p->uiH, uuid, selUuId, "myOpt1Id", kOption1Id, "optClass", "Option 1" )) != kOkRC ) + goto errLabel; + + if((rc = createOption( p->uiH, uuid, selUuId, "myOpt2Id", kOption2Id, "optClass", "Option 2" )) != kOkRC ) + goto errLabel; + + if((rc = createString( p->uiH, uuid, divUuId, "myStringId", kStringId, "stringClass", "String", "a string value" )) != kOkRC ) + goto errLabel; + + if((rc = createNumber( p->uiH, uuid, divUuId, "myNumberId", kNumberId, "numberClass", "Number", 10, 0, 100, 1, 0 )) != kOkRC ) + goto errLabel; + + if((rc = createProgress( p->uiH, uuid, divUuId, "myProgressId", kProgressId, "progressClass", "Progress", 5, 0, 10 )) != kOkRC ) + goto errLabel; + + + errLabel: + return rc; + } + + rc_t _handleUiValueMsg( ui_test_t* p, unsigned connId, unsigned parentAppId, unsigned uuId, unsigned appId, const value_t* v ) + { + rc_t rc = kOkRC; + + switch( appId ) + { + case kBtnId: + printf("Click!\n"); + break; + + case kCheckId: + printf("Check:%i\n", v->u.b); + break; + + case kSelectId: + printf("Selected: optionId:%i\n", v->u.i); + break; + + case kStringId: + printf("String: %s\n",v->u.s); + + case kNumberId: + if( v->tid == kIntTId ) + printf("Number: %i\n",v->u.i); + else + printf("Number: %f\n",v->u.d); + + } + + return rc; + } + + + rc_t _uiTestCallback( void* cbArg, unsigned connId, opId_t opId, unsigned parentAppId, unsigned uuId, unsigned appId, const value_t* v ) + { + ui_test_t* p = (ui_test_t*)cbArg; + + switch( opId ) + { + case kConnectOpId: + cwLogInfo("Connect: connId:%i.",connId); + break; + + case kDisconnectOpId: + cwLogInfo("Disconnect: connId:%i.",connId); + break; + + case kInitOpId: + _uiTestCreateUi(p,connId); + break; + + case kValueOpId: + _handleUiValueMsg( p, connId, parentAppId, uuId, appId, v ); + break; + + case kInvalidOpId: + // fall through + default: + break; + + } + return kOkRC; + } + } +} + +cw::rc_t cw::ui::test( ) +{ + rc_t rc = kOkRC; + const char* physRootDir = "/home/kevin/src/cw_rt/html/uiTest"; + const char* dfltPageFn = "index.html"; + int port = 5687; + unsigned rcvBufByteN = 2048; + unsigned xmtBufByteN = 2048; + unsigned websockTimeOutMs = 50; + const unsigned sbufN = 31; + char sbuf[ sbufN+1 ]; + ui_test_t* ui = mem::allocZ(); + + if((rc = createUi(ui->uiH, port, _uiTestCallback, ui, physRootDir, dfltPageFn, websockTimeOutMs, rcvBufByteN, xmtBufByteN )) != kOkRC ) + return rc; + + if((rc = start(ui->uiH)) != kOkRC ) + goto errLabel; + + + printf("'quit' to exit\n"); + + // readline loop + while( true ) + { + printf("? "); + if( std::fgets(sbuf,sbufN,stdin) == sbuf ) + { + printf("Sending:%s",sbuf); + + if( strcmp(sbuf,"quit\n") == 0) + break; + } + } + + errLabel: + rc_t rc1 = kOkRC; + if( ui->uiH.isValid() ) + rc1 = destroyUi(ui->uiH); + + mem::release(ui); + + return rcSelect(rc,rc1); +} diff --git a/cwUiTest.h b/cwUiTest.h new file mode 100644 index 0000000..2301076 --- /dev/null +++ b/cwUiTest.h @@ -0,0 +1,7 @@ +namespace cw +{ + namespace ui + { + rc_t test(); + } +} diff --git a/html/uiTest/css/ui.css b/html/uiTest/css/ui.css new file mode 100644 index 0000000..2d9d0dd --- /dev/null +++ b/html/uiTest/css/ui.css @@ -0,0 +1,9 @@ + + +.title_disconnected { + color: red; +} + +.title_connected { + color: green; +} diff --git a/html/uiTest/index.html b/html/uiTest/index.html new file mode 100644 index 0000000..d8fd043 --- /dev/null +++ b/html/uiTest/index.html @@ -0,0 +1,22 @@ + + + + + UI Test App + + + + + + + + +

Disconnected

+ +
+
+ + + diff --git a/html/uiTest/js/ui.js b/html/uiTest/js/ui.js new file mode 100644 index 0000000..01141b1 --- /dev/null +++ b/html/uiTest/js/ui.js @@ -0,0 +1,518 @@ +var _ws = null; +var _dfltParentId = "uiDivId"; + +function set_app_title( suffix, className ) +{ + var ele = document.getElementById('appTitleId'); + ele.innerHTML = "UI Test:" + suffix + ele.className = className +} + + +function uiOnError( msg, r) +{ + console.log("Error:" + msg); +} + +function uiGetParent( r ) +{ + parent_ele = document.getElementById(r.parent_id); + + if( parent_ele == null ) + { + uiOnError("Parent not found. parent_id:" + r.parent_id,r); + } + + return parent_ele; +} + +function uiCreateEle( r ) +{ + var parent_ele; + + if((parent_ele = uiGetParent(r)) != null ) + { + ele = document.createElement(r.ele_type) + ele.id = r.ele_id; + ele.className = r.value; + + parent_ele.appendChild(ele) + } +} + +function uiRemoveChildren( r ) +{ + ele = document.getElementById(r.ele_id) + + while (ele.firstChild) + { + ele.removeChild(ele.firstChild); + } +} + +function uiDivCreate( r ) +{ uiCreateEle(r) } + +function uiLabelCreate( r ) +{ + var parent_ele; + + if((parent_ele = uiGetParent(r)) != null ) + { + ele = document.createElement("label") + ele.htmlFor = r.ele_id + ele.innerHTML = r.value; + parent_ele.appendChild(ele) + } + +} + +function uiSelectCreate( r ) +{ + uiCreateEle(r) +} + +function uiSelectClear( r ) +{ uiRemoveChildren(r) } + +function uiSelectInsert( r ) +{ + var select_ele; + + if((select_ele = uiGetParent(r)) != null ) + { + var option = document.createElement('option'); + + option.id = r.ele_id; + option.innerHTML = r.value; + option.value = r.ele_id; + option.onclick = function() { uiOnSelectClick(this) } + + select_ele.appendChild(option) + } +} + +function uiSelectChoose( r ) +{ + var select_ele; + + if((select_ele = uiGetParent(r)) != null ) + { + if( select_ele.hasChildNodes()) + { + var children = select_ele.childNodes + for(var i=0; i 0 ) + p_ele.innerHTML = d.title + + div_ele.appendChild( p_ele ) + } + + return div_ele +} + +function ui_create_title( parent_ele, d ) +{ + return ui_create_ele( parent_ele, "label", d ); +} + +function ui_create_button( parent_ele, d ) +{ + var ele = ui_create_ctl( parent_ele, "button", null, d ); + + if( ele != null ) + { + ele.innerHTML = d.title; + ele.onclick = function() { _ws.send("value " + this.uuId + " i : 1"); } + } + + return ele +} + +function ui_create_check( parent_ele, d ) +{ + var ele = ui_create_ctl( parent_ele, "input", d.title, d ) + + if( ele != null ) + { + ele.type = "checkbox"; + + dom_set_checkbox(ele.id, d.value ); + + ele.onclick = function() { _ws.send("value" + this.uuId + " b : " + dom_get_checkbox(this.id)); } + } +} + +function ui_on_select( ele ) +{ + var s = "value " + ele.uuId + " i : " + ele.options[ ele.selectedIndex ].appId + + _ws.send( s ); +} + +function ui_create_select( parent_ele, d ) +{ + var sel_ele = ui_create_ctl( parent_ele, "select", d.title, d ); + sel_ele.onchange = function() { ui_on_select(this) } + return sel_ele; +} + +function ui_create_option( parent_ele, d ) +{ + var opt_ele = ui_create_ele( parent_ele, "option", d ); + + if( opt_ele != null ) + { + opt_ele.className = d.clas; + opt_ele.innerHTML = d.title; + } + + return opt_ele; +} + +function ui_create_string( parent_ele, d ) +{ + var ele = ui_create_ctl( parent_ele, "input", d.title, d ); + + if( ele != null ) + { + ele.value = d.value; + ele.addEventListener('keyup', function(e) { if(e.keyCode===13){ _ws.send("value" + this.uuId + " s : " + this.value + "\0");} } ); + } +} + +function ui_number_keyup( e ) +{ + console.log(e) + if( e.keyCode===13 ) + { + var ele = dom_id_to_ele(e.target.id) + console.log(ele.value) + if( ele != null ) + _ws.send("value" + ele.uuId + " i : " + ele.value); + } +} + +function ui_create_number( parent_ele, d ) +{ + var ele = ui_create_ctl( parent_ele, "input", d.title, d ); + + if( ele != null ) + { + ele.value = d.value; + ele.maxValue = d.max; + ele.minValue = d.min; + ele.stepValue = d.step; + ele.decpl = d.decpl; + ele.addEventListener('keyup', ui_number_keyup ); + } + +} + +function ui_set_progress( ele_id, value ) +{ + var ele = dom_id_to_ele(ele_id); + + ele.value = Math.round( ele.max * (value - ele.minValue) / (ele.maxValue - ele.minValue)); +} + +function ui_create_progress( parent_ele, d ) +{ + var ele = ui_create_ctl( parent_ele, "progress", d.title, d ); + + if( ele != null ) + { + ele.max = 100; + ele.maxValue = d.max; + ele.minValue = d.min; + ui_set_progress( ele.id, d.value ); + } +} + + +function ui_create( parentId, ele_type, d ) +{ + var parent_ele = ui_get_parent(parentId); + + if( parent_ele != null ) + { + switch( ele_type ) + { + case "div": + ui_create_div( parent_ele, d ) + break; + + case "title": + ui_create_title( parent_ele, d ) + break; + + case "button": + ui_create_button( parent_ele, d ) + break; + + case "check": + ui_create_check( parent_ele, d ) + break; + + case "select": + ui_create_select( parent_ele, d ); + break; + + case "option": + ui_create_option( parent_ele, d ); + break; + + case "string": + ui_create_string( parent_ele, d ); + break; + + case "number": + ui_create_number( parent_ele, d ); + break; + + case "progress": + ui_create_progress( parent_ele, d ); + break; + + default: + ui_error("Unknown UI element type: " + ele_type ) + } + } +} + + +function ws_send( d ) +{ + s = JSON.stringify(d) + //console.log(s) + _ws.send(s) +} + +function ws_on_msg( jsonMsg ) +{ + //console.log(jsonMsg) + d = JSON.parse(jsonMsg.data); + + switch( d.op ) + { + case 'create': + for (const ele_type in d.value) + { + ui_create( d.parent, ele_type, d.value[ele_type] ) + //console.log(`${ele_type}: ${d.value[ele_type]}`); + + } + + break; + } + +} + +function ws_on_open() +{ + set_app_title( "Connected", "title_connected" ); + _ws.send("init") +} + +function ws_on_close() +{ + set_app_title( "Disconnected", "title_disconnected" ); +} + +function main() +{ + _ws = new WebSocket("ws://127.0.0.1:5687/","ui_protocol") + + _ws.onmessage = ws_on_msg + _ws.onopen = ws_on_open + _ws.onclose = ws_on_close; +} + diff --git a/html/uiTest/ui.cfg b/html/uiTest/ui.cfg new file mode 100644 index 0000000..5bf4a5c --- /dev/null +++ b/html/uiTest/ui.cfg @@ -0,0 +1,15 @@ + +{ + div: { + title:"My panel", + + children: { + + button:{ id:myBtnId, title:"Push Me" }, + select:{ id:mySelectId, title:"Selector", optionL: { myId0:"Option 0", myId2:"Option 1", myId3:"Option 3" }} + + } + + } + } +} \ No newline at end of file