diff --git a/src/css/tabs/osd.css b/src/css/tabs/osd.css index 6e93db8c0..dc017637f 100644 --- a/src/css/tabs/osd.css +++ b/src/css/tabs/osd.css @@ -728,6 +728,194 @@ vertical-align: top; } +/* Custom Element cards */ +.ce-card { + border: 1px solid #ddd; + border-radius: 4px; + margin-bottom: 6px; + background: #fff; + border-left: 3px solid #ddd; +} + +.ce-card-configured { + border-left-color: #37a8db; +} + +.ce-card-collapsed { + border-left-color: #ccc; +} + +.ce-card-header { + display: flex; + align-items: center; + padding: 6px 10px; + cursor: pointer; + gap: 8px; + user-select: none; +} + +.ce-card-header:hover { + background: #f5f5f5; +} + +.ce-card-header .ios7-switch { + flex-shrink: 0; +} + +.ce-card-name { + font-weight: bold; + font-size: 12px; + white-space: nowrap; +} + +.ce-card-preview { + color: #888; + font-size: 11px; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: right; +} + +.ce-card-chevron { + font-size: 14px; + color: #888; + flex-shrink: 0; + width: 16px; + text-align: center; +} + +.ce-card-body { + padding: 8px 10px; + border-top: 1px solid #eee; +} + +.ce-slot-row { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 4px; +} + +.ce-slot-row:last-child { + margin-bottom: 0; +} + +.ce-source-select { + width: auto !important; + flex-shrink: 0; +} + +.ce-format-select { + width: auto !important; + flex-shrink: 0; +} + +.ce-slot-value { + flex: 1; + min-width: 0; +} + +.ce-slot-value input, +.ce-slot-value select { + width: 100% !important; +} + +/* Icon picker popup */ +.ce-icon-picker-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; +} + +.ce-icon-picker-popup { + background: #fff; + border-radius: 6px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + padding: 12px; + max-width: 520px; + max-height: 80vh; + overflow-y: auto; +} + +.ce-icon-picker-title { + font-weight: bold; + margin-bottom: 8px; + font-size: 13px; +} + +.ce-icon-picker-grid { + display: grid; + grid-template-columns: repeat(16, 1fr); + gap: 2px; +} + +.ce-icon-picker-tile { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid #ddd; + border-radius: 3px; + cursor: pointer; + background: #fafafa; +} + +.ce-icon-picker-tile:hover { + border-color: #37a8db; + background: #e8f4fd; +} + +.ce-icon-picker-tile.ce-icon-picker-selected { + border-color: #37a8db; + background: #d0ecfa; + box-shadow: 0 0 0 1px #37a8db; +} + +.ce-icon-picker-tile img { + max-width: 20px; + max-height: 20px; + image-rendering: pixelated; +} + +/* Icon picker inline button */ +.ce-ico-picker-btn { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border: 1px solid silver !important; + border-radius: 3px; + cursor: pointer; + background: #fafafa; + min-height: 24px; +} + +.ce-ico-picker-btn:hover { + border-color: #37a8db !important; + background: #e8f4fd; +} + +.ce-ico-preview { + width: 16px; + height: 16px; + image-rendering: pixelated; +} + +.ce-ico-label { + font-size: 11px; + color: #666; +} + .osdCustomElement_main_table { width: 100%; table-layout: fixed; diff --git a/tabs/osd.js b/tabs/osd.js index 563d4d29c..f2a2724dd 100644 --- a/tabs/osd.js +++ b/tabs/osd.js @@ -917,6 +917,14 @@ OSD.constants = { }, ], + // Custom Element format options for GV/LC source types + CE_FORMATS: [ + {label: '0', offset: 0}, {label: '00', offset: 1}, {label: '000', offset: 2}, + {label: '0000', offset: 3}, {label: '00000', offset: 4}, {label: '0.0', offset: 5}, + {label: '0.00', offset: 6}, {label: '00.0', offset: 7}, {label: '00.00', offset: 8}, + {label: '000.0', offset: 9}, {label: '000.00', offset: 10}, {label: '0000.0', offset: 11}, + ], + // All display fields, from every version, do not remove elements, only add! ALL_DISPLAY_GROUPS: [ { @@ -2999,6 +3007,13 @@ OSD.GUI.updateFields = function(event) { } OSD.GUI.saveItem(item); + + // Sync card header toggle if applicable + var ceMatch = item.name.match(/^CUSTOM_ELEMENT_(\d+)$/); + if (ceMatch) { + var $card = $('.ce-card[data-ce-index="' + (parseInt(ceMatch[1]) - 1) + '"]'); + $card.find('.ce-card-header input[type="checkbox"]').prop('checked', itemData.isVisible); + } }) ); @@ -3058,6 +3073,9 @@ OSD.GUI.updateFields = function(event) { updatePilotAndCraftNames(); updatePanServoPreview(); } + + // Inject custom element cards into the left panel group + injectCustomElementCards(); }; OSD.GUI.removeBottomLines = function(){ @@ -3751,108 +3769,401 @@ TABS.osd.initialize = function (callback) { }); }; +// Icon picker popup — shows a grid of OSD font characters for selection +function openIconPicker($targetInput) { + // Close any existing picker + $('.ce-icon-picker-overlay').remove(); + + var $overlay = $('
').addClass('ce-icon-picker-overlay').on('click', function() { + $(this).remove(); + }); + + var $popup = $('
').addClass('ce-icon-picker-popup').on('click', function(e) { + e.stopPropagation(); + }); + $popup.append($('
').addClass('ce-icon-picker-title').text('Select OSD Icon')); + + var $grid = $('
').addClass('ce-icon-picker-grid'); + var currentVal = parseInt($targetInput.val()) || 0; + + for (var c = 1; c <= 255; c++) { + var url = (FONT.data && FONT.data.character_image_urls[c]) ? FONT.draw(c) : ''; + var $tile = $('
').addClass('ce-icon-picker-tile') + .attr('data-char', c) + .attr('title', '#' + c) + .append($('').attr('src', url)); + if (c === currentVal) { + $tile.addClass('ce-icon-picker-selected'); + } + $tile.on('click', function() { + var val = parseInt($(this).attr('data-char')); + $targetInput.val(val).trigger('change'); + $overlay.remove(); + }); + $grid.append($tile); + } + + $popup.append($grid); + $overlay.append($popup); + $('body').append($overlay); +} + +// Convert source index (0-6) + format index to the flat type value (0-28) +// Sources: 0=None, 1=Text, 2=Icon Static, 3=Icon GV, 4=Icon LC, 5=GV, 6=LC +function ceSourceFormatToType(source, formatIndex) { + if (source <= 4) return source; // None, Text, Icon Static, Icon GV, Icon LC + if (source === 5) return 5 + formatIndex; // GV: types 5-16 + if (source === 6) return 17 + formatIndex; // LC: types 17-28 + return 0; +} + +// Convert flat type value (0-28) to {source, formatIndex} +function ceTypeToSourceFormat(type) { + if (type <= 4) return {source: type, formatIndex: 0}; + if (type <= 16) return {source: 5, formatIndex: type - 5}; // GV + if (type <= 28) return {source: 6, formatIndex: type - 17}; // LC + return {source: 0, formatIndex: 0}; +} + function createCustomElements(){ if(FC.OSD_CUSTOM_ELEMENTS.settings.customElementsCount == 0){ - $('.custom-element-container').remove(); + $('.custom-element-container').hide(); return; } - + // Hide the right panel — configuration moves to left-panel cards + $('.custom-element-container').hide(); $('#INAVCharacterMapDocURL').attr('href', globalSettings.configuratorTreeLocation + 'resources/osd/INAV%20Character%20Map.md'); +} - var customElementsContainer = $('#osdCustomElements'); - var init = true; - - for(var i = 0; i < FC.OSD_CUSTOM_ELEMENTS.settings.customElementsCount; i++){ - var label = $('