From f566198285ac5b83954321c4783aa5066ff3bbee Mon Sep 17 00:00:00 2001 From: kevin Date: Sat, 17 Feb 2024 08:27:18 -0500 Subject: [PATCH] Initial working version. --- Makefile.am | 8 +- build/linux/debug/build.sh | 2 - src/proj/html/css/preset_sel.css | 59 ++ src/proj/html/css/ui.css | 148 ++++ src/proj/html/index.html | 24 + src/proj/html/js/ui.js | 1201 ++++++++++++++++++++++++++++++ src/proj/html/ui.cfg | 25 + src/proj/main.cfg | 103 ++- src/proj/main.cpp | 252 ++++++- 9 files changed, 1799 insertions(+), 23 deletions(-) create mode 100644 src/proj/html/css/preset_sel.css create mode 100644 src/proj/html/css/ui.css create mode 100644 src/proj/html/index.html create mode 100644 src/proj/html/js/ui.js create mode 100644 src/proj/html/ui.cfg diff --git a/Makefile.am b/Makefile.am index b39bb6e..b234f34 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,4 @@ ACLOCAL_AMFLAGS = -I m4 # use custom macro's in ./m4 - lib_LTLIBRARIES= bin_PROGRAMS= include_HEADERS= @@ -16,9 +15,6 @@ include_HEADERS= # -Wno-multichar - turns off multi-character constant warnings from cmAudioFile.c -WS_DIR = $(HOME)/sdk/libwebsockets/build/out - - AM_CPPFLAGS = -I.. -I$(srcdir)/src/libcw AM_CFLAGS = -Wno-multichar AM_CXXFLAGS = -Wno-multichar @@ -38,7 +34,7 @@ endif if OS_LINUX if OS_64 - AM_LDFLAGS += -L/usr/lib64 + AM_LDFLAGS += -L/usr/lib64/atlas -L/usr/lib64 AM_CFLAGS += -m64 endif @@ -86,8 +82,6 @@ if cwWEB endif if cwWEBSOCK - # AM_CPPFLAGS += -I$(WS_DIR)/include - # AM_LDFLAGS += -L$(WS_DIR)/lib src_proj_proj_LDADD += -lwebsockets endif diff --git a/build/linux/debug/build.sh b/build/linux/debug/build.sh index 80c1641..d63e7c9 100755 --- a/build/linux/debug/build.sh +++ b/build/linux/debug/build.sh @@ -16,8 +16,6 @@ cd ${curdir} --enable-alsa \ CFLAGS="-g -Wall" \ CXXFLAGS="-g -Wall" \ -CPPFLAGS="-I${HOME}/sdk/libwebsockets/build/out/include" \ -LDFLAGS="-L${HOME}/sdk/libwebsockets/build/out/lib" \ LIBS= diff --git a/src/proj/html/css/preset_sel.css b/src/proj/html/css/preset_sel.css new file mode 100644 index 0000000..5ae227f --- /dev/null +++ b/src/proj/html/css/preset_sel.css @@ -0,0 +1,59 @@ + + +.fragList { + border: 1px solid LightSteelBlue; + width: 1000px; + height: 450px; +} + +.fragList label { + width: 50px; + background-color: LightBlue; +} + +.fragList .uiNumbDisp { + width: 25px; +} + +.fragPanel { + border: 1px solid LightSteelBlue; + padding-bottom: 5px; + padding-top: 5px; +} + +.fragPanelRow { + padding-right: 15px; + padding-left: 15px; + justify-content: space-around; +} + +.fragPresetCtl { + width: 50px; +} + +.fragPresetCtl:nth-of-type(even){ + border: 1px solid Yellow; +} + +.fragPresetCtl .uiNumber { + border: 1px solid LightSteelBlue; + width: 80%; +} + +.fragPresetCtl .uiString { + border: 1px solid LightSteelBlue; + width: 80%; +} + +.fragNote { + width: 575px; +} + +.uiRow { + padding-bottom: 5px; + padding-top: 5px; +} + +.velTunerPanel { + border: 1px solid black; +} diff --git a/src/proj/html/css/ui.css b/src/proj/html/css/ui.css new file mode 100644 index 0000000..7c98619 --- /dev/null +++ b/src/proj/html/css/ui.css @@ -0,0 +1,148 @@ + +body { + background-color: LightCyan; +} + + +.title_disconnected { + color: red; +} + +.title_connected { + color: green; +} + +input, label, button, select { + font-family: sans-serif; + font-size: 10px; + height: 20px; +} + + +div { + /*border: 1px solid black; */ + background-color: LightSteelBlue; +} + +div p { + font-family: sans-serif; + font-size: 12px; + margin-top: 2px; + margin-bottom: 2px; + background-color: LightSteelBlue; +} + +label { + /*border: 1px solid red;*/ + width: 50px; + padding-left: 10px; + margin-top: 3px; +} + + + +.uiCtlDiv { + display: flex; + flex-direction: row; + align-items: center; + background-color: LightBlue; +} + +.uiCtlDiv input { + background-color: PowderBlue; +} + +.uiNumberDiv input { + width: 50px; +} + +.uiPanel { + background-color: LightBlue; +} + +.uiRow { + display: flex; + flex-direction: row; + align-items: center; + background-color: LightBlue; +} + +.uiCol { + display: flex; + flex-direction: column; + align-items: center; + background-color: LightBlue; +} + + +/* outer log div - contains the log label and the log scroller */ +.uiLogDiv { + display: flex; + flex-direction: column; + + align-items: flex-start; /* left justify */ + align-content: flex-stretch; /* fill horizontal space */ + +} + +.uiLogDiv label { + width: 100%; + background-color: LightSteelBlue; + +} + + +/* log scroller */ +.uiLog { + display:flex; + flex-direction: column; + + height: 150px; + overflow-x: hidden; /* 'hidden' to remove x scroll bar */ + overflow-y: auto; + width: 100%; + + background-color: PowderBlue; + +} + +/* The log text */ +.uiLog pre { +} + + +/* outer list div - contains the list label and the list scroller */ +.uiListDiv { + display: flex; + flex-direction: column; + + align-items: flex-start; /* left justify */ + align-content: flex-stretch; /* fill horizontal space */ + +} + +.uiListDiv label { + width: 100%; + background-color: LightSteelBlue; + +} + +.uiList { + display:flex; + flex-direction: column; + + height: 100px; + overflow-x: hidden; /* 'hidden' to remove scroll bar */ + overflow-y: auto; + width: 100%; + + background-color: PowderBlue; +} + +.uiStringDisp { + width: 100%; +} + +.uiSelected { + border: 1px solid blue; +} diff --git a/src/proj/html/index.html b/src/proj/html/index.html new file mode 100644 index 0000000..a85daaa --- /dev/null +++ b/src/proj/html/index.html @@ -0,0 +1,24 @@ + + + + + Preset Selection App + + + + + + + + + +
+

Preset Selection:

+

Disconnected

+
+ + + + diff --git a/src/proj/html/js/ui.js b/src/proj/html/js/ui.js new file mode 100644 index 0000000..5d3031d --- /dev/null +++ b/src/proj/html/js/ui.js @@ -0,0 +1,1201 @@ +var _ws = null; +var _rootId = "0"; +var _nextEleId = 0; +var _focusId = null; +var _focusVal = null; +var _rootDivEle = null; +var _rootEle = null; + +function set_app_title( suffix, className ) +{ + var ele = document.getElementById('connectTitleId'); + if(ele != null) + { + ele.innerHTML = suffix + ele.className = className + } + else + { + console.log("Ele. not found. Set title failed.") + } +} + + +function dom_child_by_id( parentEle, child_id ) +{ + var childrenL = parentEle.children + for(var i=0; i 0) + { + label_ele = dom_create_ele("label"); + + label_ele.innerHTML = label; + + div_ele.appendChild(label_ele) + } + } + + return ui_create_ele( div_ele, ele_type, d, dfltEleClassName ); +} + +function ui_create_div( parent_ele, d ) +{ + var div_ele = ui_create_ele( parent_ele, "div", d, "uiDiv" ); + + if( div_ele != null ) + { + + if( d.title != null ) + { + var title = d.title.trim() + + if( title.length > 0 ) + { + var p_ele = dom_create_ele("p") + + p_ele.innerHTML = title + + div_ele.appendChild( p_ele ) + } + } + } + + return div_ele; +} + +function ui_create_panel_div( parent_ele, d ) +{ + d.type = "div" + + if( !d.hasOwnProperty('className') ) + d.className = "uiPanel" + + var div_ele = ui_create_div( parent_ele, d ); + + + + return div_ele +} + +function ui_create_row_div( parent_ele, d ) +{ + d.type = "div" + + if( !d.hasOwnProperty('className') ) + d.className = "uiRow" + + var div_ele = ui_create_div( parent_ele, d ); + + + return div_ele +} + +function ui_create_col_div( parent_ele, d ) +{ + d.type = "div" + + if( !d.hasOwnProperty('className') ) + d.className = "uiCol" + + var div_ele = ui_create_div( parent_ele, d ); + + + return div_ele +} + + +function ui_create_label( parent_ele, d ) +{ + var ele = ui_create_ele( parent_ele, "label", d, "uiLabel" ); + + if( ele != null ) + { + ele.innerHTML = d.title; + } + + return ele; +} + +function ui_create_button( parent_ele, d ) +{ + var ele = ui_create_ctl( parent_ele, "button", null, d, "uiButton" ); + + if( ele != null ) + { + ele.innerHTML = d.title; + ele.onclick = function() { ui_send_int_value(this,1); } + } + + return ele; +} + +function ui_create_check( parent_ele, d ) +{ + var ele = ui_create_ctl( parent_ele, "input", d.title, d, "uiCheck" ) + + if( ele != null ) + { + ele.type = "checkbox"; + + ele.onclick = function() { ui_send_bool_value(this,dom_get_checkbox(this.id)); } + + if( !d.hasOwnProperty('value') ) + { + ui_send_echo(ele) + } + else + { + dom_set_checkbox(ele.id, d.value ); + ui_send_bool_value(ele,dom_get_checkbox(ele.id)) + } + + } + return ele; +} + +// +// Note: The value of a 'select' widget is always set by the 'appId' +// of the selected 'option'. Likewise the 'appId' of the selected +// option is returned as the value of the select widget. +// +function ui_on_select( ele ) +{ + ui_send_int_value(ele,ele.options[ ele.selectedIndex ].appId); +} + +function ui_select_set_from_option_app_id( sel_ele, option_appId ) +{ + var i; + for(i=0; i= ele.minValue) + var max_ok_fl = (!ele.hasOwnProperty('maxValue')) || (value <= ele.maxValue) + + if( min_ok_fl && max_ok_fl ) + { + ele.value = value; + if( ele.decpl == 0 ) + ui_send_int_value( ele, ele.value ) + else + ui_send_float_value( ele, ele.value ) + } + else + { + ui_error("Number value " + value + " out of range. min:" + ele.minValue + " max:" +ele.maxValue ) + } + +} + +function ui_set_number_range( ele, d ) +{ + _ui_set_number_range(ele,d) + if( d.hasOwnProperty('value') ) + ui_set_number_value(ele,d.value) +} + +function ui_create_number( parent_ele, d ) +{ + var ele = ui_create_ctl( parent_ele, "input", d.title, d, "uiNumber" ); + + if( ele != null ) + { + ele.addEventListener('keyup', ui_number_keyup ); + ele.addEventListener('focus', function(e) { _ui_on_focus(this); } ); + ele.addEventListener('blur', function(e) { _ui_on_number_blur(this); } ); + _ui_set_number_range(ele,d) + + + if( d.hasOwnProperty('value') ) + { + ui_set_number_value(ele,d.value) + } + else + { + ui_send_echo(ele); + } + } + return ele; +} + +function ui_set_number_display( ele_id, value ) +{ + //console.log("Numb disp: " + ele_id + " " + value) + + var ele = dom_id_to_ele(ele_id); + + if( typeof(value)=="number") + { + var val = value.toString(); + + if( ele.decpl == 0 ) + ele.innerHTML = parseInt(val,10); + else + ele.innerHTML = parseFloat(val); + } +} + +function ui_create_number_display( parent_ele, d ) +{ + var ele = ui_create_ctl( parent_ele, "label", d.title, d, "uiNumbDisp" ); + + if( ele != null ) + { + ele.decpl = d.decpl; + + if( d.hasOwnProperty('value') ) + { + ui_set_number_display(ele.id, d.value); + } + else + { + ui_send_echo(ele); + } + } + + return ele; + +} + +function ui_create_text_display( parent_ele, d ) +{ + return ui_create_ctl( parent_ele, "label", d.title, d, "uiTextDisp" ); +} + + +function ui_set_progress( ele, value ) +{ + ele.value = Math.round( ele.max * (value - ele.minValue) / (ele.maxValue - ele.minValue)); +} + +function _ui_set_prog_range( ele, d ) +{ + ele.maxValue = d.max; + ele.minValue = d.min; +} + +function ui_create_progress( parent_ele, d ) +{ + var ele = ui_create_ctl( parent_ele, "progress", d.title, d, "uiProgress" ); + + if( ele != null ) + { + ele.max = 100; + _ui_set_prog_range(ele,d) + + if( !d.hasOwnProperty('value') ) + ui_send_echo(ele); + else + { + ui_set_progress( ele, d.value ); + ui_send_int_value( ele, ele.value ); + } + + } + return ele +} + +function ui_set_prog_range( ele, d ) +{ + _ui_set_prog_range(ele,d) + if( d.hasOwnProperty('value')) + ui_set_progress(ele,d.value) +} + +function _on_log_click( evt ) +{ + var pre_ele = dom_id_to_ele(evt.target.id) + + pre_ele.auto_scroll_flag = !pre_ele.auto_scroll_flag; +} + +function ui_set_log_text( ele, value ) +{ + var child_id = ele.id + "_pre" + + for(var i=0; i to the containing div + var ele = dom_create_ele("pre") + + ele.id = log_ele.id + "_pre" + ele.onclick = _on_log_click; + ele.auto_scroll_flag = true; + + log_ele.appendChild(ele) + + return log_ele +} + +function ui_create_list( parent_ele, d ) +{ + //console.log(d) + var list_ele = ui_create_ctl( parent_ele, "div", d.title, d, "uiList" ) + + return list_ele +} + +function ui_set_value( d ) +{ + var eleId = d.uuId.toString() + var ele = dom_id_to_ele(eleId) + + if( ele == null ) + console.log("ele '"+eleId+"' not found"); + else + { + if( !ele.hasOwnProperty("uiEleType") ) + { + console.log("No type"); + } + } + + if( ele != null && ele.hasOwnProperty("uiEleType")) + { + //console.log("found: "+ele.uiEleType) + + switch( ele.uiEleType ) + { + case "div": + break; + + case "label": + ele.innerHTML = d.value + break; + + case "button": + break; + + case "check": + dom_set_checkbox(ele.id,d.value) + break; + + case "select": + ui_select_set_from_option_app_id(ele,d.value) + break; + + case "option": + break; + + case "str_disp": + ui_set_str_display(ele.id,d.value); + break + + case "string": + ele.value = d.value + break; + + case "number": + ele.value = d.value + break; + + case "numb_disp": + ui_set_number_display(ele.id,d.value); + break; + + case "progress": + ui_set_progress( ele, d.value ) + //ele.value = d.value + break; + + case "log": + ui_set_log_text( ele, d.value ) + break + + default: + ui_error("Unknown UI element type on set value: " + d.type ) + } + } +} + +function _ui_modify_class( ele, classLabelArg, enableFl ) +{ + let classLabel = " " + classLabelArg; // prefix the class label with a space + + //console.log(ele.id + " " + classLabelArg + " " + enableFl ) + + let isEnabledFl = false; + + if( ele.hasOwnProperty("className") ) + isEnabledFl = ele.className.includes(classLabel) + else + ele.className = "" + + // if the class is not already enabled/disabled + if( enableFl != isEnabledFl ) + { + if( enableFl ) + ele.className += classLabel; + else + ele.className = ele.className.replace(classLabel, ""); + } + + //console.log(ele.id + " " + ele.className + " " + enableFl ) +} + +function ui_set_select( ele, enableFl ) +{ + _ui_modify_class(ele,"uiSelected",enableFl) + ui_send_select( ele, enableFl ) +} + + +function ui_set_clickable( ele, enableFl ) +{ + ele.clickableFl = enableFl + + if(enableFl) + ele.onclick = function( evt ){ ui_on_click( this, evt ); } + else + ele.onclick = null +} + +function ui_set_visible( ele, enableFl ) +{ + if(enableFl) + { + if(ele.hasOwnProperty("style_display") ) + { + ele.style.display = ele.style_display; + } + else + { + ele.style.display = "block"; + } + } + else + { + ele.style_display = ele.style.display; + ele.style.display = "none"; + } +} + +function ui_set_enable( ele, enableFl ) +{ + ele.disabled = !enableFl +} + +function ui_set_order_key(ele, orderKey) +{ + let parent = ele.parentElement // get the parent of the element to reorder + ele = parent.removeChild( ele ) // remove the element to reorder from the parent list + + ele.order = orderKey + + let i = 0; + for(i=0; i= orderKey) + { + parent.insertBefore( ele, parent.children[i] ) + break + } + } + + // no element was found greater than this element .... + if( i == parent.children.length ) + parent.appendChild(ele) // ... insert the element at the end of the child lsit + +} + +function ui_set( d ) +{ + //console.log(d) + var ele = dom_id_to_ele(d.uuId.toString()) + + if( ele == null ) + console.log("ele not found"); + + if( ele != null) + { + switch( d.type ) + { + case "number_range": + ui_set_number_range(ele, d) + break; + + case "progress_range": + ui_set_prog_range(ele, d) + break; + + case "select": + ui_set_select(ele,d.value) + break + + case "clickable": + ui_set_clickable(ele,d.value) + break + + case "visible": + ui_set_visible(ele,d.value) + break + + case "enable": + ui_set_enable(ele,d.value) + break + + case "order": + ui_set_order_key(ele,d.value) + break + + } + } +} + +function ui_cache( d ) +{ + for(i=0; iioH ); + break; + + case kIoReportBtnId: + io::report(app->ioH); + break; + + case kNetPrintBtnId: + break; + + case kReportBtnId: + break; + + case kLatencyBtnId: + latency_measure_report(app->ioH); + latency_measure_setup(app->ioH); + break; + + case kValueNumbId: + app->value = m.value->u.u; + cwLogInfo("Setting value:%i",app->value); + break; + + } + return kOkRC; +} + +rc_t _ui_echo_callback(app_t* app, const io::ui_msg_t& m ) +{ + switch( m.appId ) + { + case kValueNumbId: + { + uiSendValue( app->ioH, io::uiFindElementUuId( app->ioH, kValueNumbId ), app->value ); + } + break; + + } + return kOkRC; +} + +rc_t _ui_callback( app_t* app, const io::ui_msg_t& m ) { rc_t rc = kOkRC; - object_t* cfg = nullptr; - cw::log::createGlobal(); - - cwLogInfo("Project template: args:%i", argc); + + switch( m.opId ) + { + case ui::kConnectOpId: + cwLogInfo("UI Connected: wsSessId:%i.",m.wsSessId); + break; + + case ui::kDisconnectOpId: + cwLogInfo("UI Disconnected: wsSessId:%i.",m.wsSessId); + break; + + case ui::kInitOpId: + cwLogInfo("UI Init."); + break; + case ui::kValueOpId: + _ui_value_callback( app, m ); + break; + + case ui::kCorruptOpId: + cwLogInfo("UI Corrupt."); + break; + + case ui::kClickOpId: + cwLogInfo("UI Click."); + break; + + case ui::kSelectOpId: + cwLogInfo("UI Select."); + break; + + case ui::kEchoOpId: + _ui_echo_callback( app, m ); + break; + + case ui::kIdleOpId: + break; + + case ui::kInvalidOpId: + // fall through + default: + assert(0); + break; + + } + + return rc; +} + + +rc_t _io_callback( void* arg, const io::msg_t* m ) +{ + app_t* app = (app_t*)arg; + + switch( m->tid ) + { + case io::kThreadTId: + break; + + case io::kTimerTId: + break; + + case io::kSerialTId: + break; + + case io::kMidiTId: + break; + + case io::kAudioTId: + break; + + case io::kAudioMeterTId: + break; + + case io::kSockTId: + break; + + case io::kWebSockTId: + break; + + case io::kUiTId: + _ui_callback(app,m->u.ui); + break; + + case io::kExecTId: + break; + + default: + assert(0); + } + + return kOkRC; +} + +rc_t _parse_cfg( app_t& app, int argc, char* argv[] ) +{ + rc_t rc = kOkRC; + if( argc < 2 || textLength(argv[1])==0 ) { - cwLogError(kInvalidArgRC,"No cfg. file was given."); + rc = cwLogError(kInvalidArgRC,"No cfg. file was given."); goto errLabel; } else { - const char* val = nullptr; - - if((rc = objectFromFile(argv[1],cfg)) != kOkRC ) + if((rc = objectFromFile(argv[1],app.cfg)) != kOkRC ) { - cwLogError(rc,"The file '%s'.",argv[1]); + rc = cwLogError(rc,"The file '%s'.",argv[1]); goto errLabel; } - if((rc = cfg->getv("param",val)) != kOkRC ) + if((rc = app.cfg->getv("param", app.value, + "libcw", app.io_cfg)) != kOkRC ) { - cwLogError(kSyntaxErrorRC,"The 'param' cfg. field was not found."); + rc = cwLogError(kSyntaxErrorRC,"The 'param' cfg. field was not found."); goto errLabel; } - cwLogInfo("param=%s",cwStringNullGuard(val)); + } +errLabel: + if( rc != kOkRC ) + rc = cwLogError(rc,"App. cfg. parse failed."); + return rc; +} + +int main( int argc, char* argv[] ) +{ + rc_t rc = kOkRC; + app_t app = {}; + cw::log::createGlobal(); + + cwLogInfo("Project template: args:%i", argc); + + if((rc = _parse_cfg(app,argc,argv)) != kOkRC ) + goto errLabel; + + if((rc = create( app.ioH, app.io_cfg, _io_callback, &app, appIdMapA, appIdMapN ) ) != kOkRC ) + { + rc = cwLogError(rc,"IO create failed."); + goto errLabel; } + + // start the IO framework instance + if((rc = io::start(app.ioH)) != kOkRC ) + { + rc = cwLogError(rc,"Preset-select app start failed."); + goto errLabel; + } + + //io::uiReport(app.ioH); + + + // execute the io framework + while( !io::isShuttingDown(app.ioH)) + { + // This call will block on the websocket handle + // for up to io_cfg->ui.websockTimeOutMs milliseconds + io::exec(app.ioH); + + + } + + // stop the io framework + if((rc = io::stop(app.ioH)) != kOkRC ) + { + rc = cwLogError(rc,"IO API stop failed."); + goto errLabel; + } + + errLabel: + destroy(app.ioH); + if( app.cfg != nullptr ) + app.cfg->free(); cw::log::destroyGlobal(); return 0;