diff --git a/README.md b/README.md index db98bdd64..f2c0e8108 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ If you don't want to build Hide from source, you can download a nightly build fr #### 1. Install Prerequisites - Install [Haxe](https://haxe.org/) using appropriate installer from https://haxe.org/download/ - Install these libraries: -``` +```shell +haxelib git format https://github.com/HaxeFoundation/format haxelib git heaps https://github.com/HeapsIO/heaps haxelib git castle https://github.com/ncannasse/castle haxelib git hxbit https://github.com/ncannasse/hxbit @@ -30,6 +31,7 @@ haxelib git hscript https://github.com/HaxeFoundation/hscript haxelib git hxnodejs https://github.com/HaxeFoundation/hxnodejs haxelib git domkit https://github.com/HeapsIO/domkit haxelib git hx3compat https://github.com/HaxeFoundation/hx3compat +haxelib git hashlink https://github.com/HaxeFoundation/hashlink master other/haxelib/ ``` #### 2. Build Heaps IDE diff --git a/bin/cdb.css b/bin/cdb.css index 547437a3e..82a08185a 100644 --- a/bin/cdb.css +++ b/bin/cdb.css @@ -9,9 +9,6 @@ padding: 0 5px 0 5px; border-top: solid 1px black; border-bottom: solid 1px black; - position: sticky; - z-index: 50; - top: 0; background-color: #222222; } .cdb-view .top-bar span { @@ -45,18 +42,29 @@ color: #e12729; } .cdb-view .cdb { - margin-bottom: 350px; + overflow-y: scroll; +} +.cdb-view .cdb > table { + margin-bottom: 200px; } .cdb-view.cdb-diff .cdb-sheet th, .cdb-view.cdb-diff th.start { background-color: #d60; } .cdb { - background-color: black; + height: 100%; } .cdb:focus { outline: none; } +.cdb .cdb-sheet { + overflow-x: auto; + width: 100%; + table-layout: fixed; + border-spacing: 0; + background-color: #222; + position: relative; +} .cdb .cdb-sheet td.cursorView { outline: 2px solid #ccc !important; outline-offset: -1px; @@ -104,14 +112,6 @@ .cdb .cdb-sheet td.selected.left { border-left: 1px solid #ccc; } -.cdb .cdb-sheet { - width: 100%; - table-layout: fixed; - border-collapse: collapse; - border-spacing: 0; - background-color: #222; - position: relative; -} .cdb .cdb-sheet th.start { width: 30px; border-left-color: #333; @@ -122,16 +122,18 @@ cursor: pointer; background-color: #333; border-left: none; + border-bottom: 1px solid #444; } .cdb .cdb-sheet tr.head td.start { cursor: nw-resize; } .cdb .cdb-sheet tr.separator { + --level: 0; height: 10px; } .cdb .cdb-sheet tr.separator td { position: sticky; - top: 39px; + top: 21px; z-index: 2; border-left: none; padding-top: 6px; @@ -147,41 +149,11 @@ .cdb .cdb-sheet tr.separator td .toggle:hover { color: #ccc; } -.cdb .cdb-sheet tr.separator.seplevel-1 td { - background-color: #3d3d3d; -} -.cdb .cdb-sheet tr.separator.seplevel-1 td .toggle { - padding-left: 25px; -} -.cdb .cdb-sheet tr.separator.seplevel-2 td { - background-color: #383838; -} -.cdb .cdb-sheet tr.separator.seplevel-2 td .toggle { - padding-left: 40px; -} -.cdb .cdb-sheet tr.separator.seplevel-3 td { - background-color: #343434; -} -.cdb .cdb-sheet tr.separator.seplevel-3 td .toggle { - padding-left: 50px; -} -.cdb .cdb-sheet tr.separator.seplevel-4 td { - background-color: #303030; -} -.cdb .cdb-sheet tr.separator.seplevel-4 td .toggle { - padding-left: 60px; -} -.cdb .cdb-sheet tr.separator.seplevel-5 td { - background-color: #303030; -} -.cdb .cdb-sheet tr.separator.seplevel-5 td .toggle { - padding-left: 70px; -} -.cdb .cdb-sheet tr.separator.seplevel-6 td { - background-color: #303030; +.cdb .cdb-sheet tr.separator td { + background-color: #NaNNaNNaN; } -.cdb .cdb-sheet tr.separator.seplevel-6 td .toggle { - padding-left: 80px; +.cdb .cdb-sheet tr.separator td .toggle { + padding-left: calc(25px + var(--level) * 20px); } .cdb .cdb-sheet tr.locIgnored { background-color: #221C1C; @@ -237,19 +209,25 @@ .cdb .cdb-sheet tr.clickable:hover { background-color: #f8f8f8; } -.cdb .cdb-sheet:not(.cdb-sub-sheet) > thead.head > th { +.cdb .cdb-sheet thead.head { + background-color: #2A2A2A; + z-index: 9; +} +.cdb .cdb-sheet:not(.cdb-sub-sheet) > thead.head { position: sticky; - z-index: 600; - top: 20px; + top: 0; +} +.cdb .cdb-sheet .cdb-sub-sheet > thead.head { + top: 21px; + z-index: 10; } .cdb .cdb-sheet tr.head, .cdb .cdb-sheet th { font-weight: bold; text-align: center; padding: 1px 10px 4px 10px; - box-shadow: inset 0px -1px 1px #666; - border-left: 1px solid #444; - background-color: #222; + border-left: 1px solid #666; + border-bottom: 1px solid #666; color: #ddd; width: auto; white-space: nowrap; @@ -263,8 +241,6 @@ .cdb .cdb-sheet td { user-select: none; border-left: 1px solid #444; -} -.cdb .cdb-sheet tr { border-bottom: 1px solid #333; } .cdb .cdb-sheet tr.validation-error td { @@ -397,7 +373,6 @@ outline: 1px solid black; } .cdb .cdb-sheet td.t_flags { - position: relative; overflow: visible; } .cdb .cdb-sheet td.t_ref, @@ -532,6 +507,7 @@ border: 1px solid black; padding: 5px; z-index: 810; + max-width: 500px; } .cdb .cdb-sheet td.t_file .preview .previewContent .label { text-align: center; @@ -539,6 +515,9 @@ background-color: #444; font-family: monospace; } +.cdb .cdb-sheet td.t_file .preview .previewContent > img { + width: 100%; +} .cdb .cdb-sheet td.t_file .inlineImage > img { max-height: 100px; } diff --git a/bin/cdb.less b/bin/cdb.less index c4ba01ece..d7b87a06b 100644 --- a/bin/cdb.less +++ b/bin/cdb.less @@ -16,9 +16,6 @@ padding: 0 5px 0 5px; border-top: solid @border-size @border-color; border-bottom: solid @border-size @border-color; - position: sticky; - z-index: 50; - top: 0; background-color: #222222; span { @@ -64,8 +61,14 @@ } .cdb { - margin-bottom: 350px; + overflow-y: scroll; + + > table { + margin-bottom: 200px; + } } + + } .cdb-view.cdb-diff { @@ -75,14 +78,16 @@ } .cdb { - - background-color : black; + height: 100%; + //background-color : black; &:focus { outline: none; } .cdb-sheet { + overflow-x: auto; + td.cursorView { outline: 2px solid #ccc !important; outline-offset: -1px; @@ -120,14 +125,10 @@ &.bot { border-bottom: 1px solid #ccc; } &.left { border-left: 1px solid #ccc; } } - } - - - .cdb-sheet { width : 100%; table-layout:fixed; - border-collapse : collapse; + //border-collapse : collapse; border-spacing : 0; background-color: #222; position: relative; // so that error message follow along with scrolling @@ -143,6 +144,7 @@ cursor : pointer; background-color: #333; border-left: none; + border-bottom : 1px solid #444; } tr.head td.start { @@ -150,10 +152,11 @@ } tr.separator { + --level: 0; height : 10px; td { position: sticky; - top: 39px; + top: 21px; z-index: 2; border-left: none; padding-top: 6px; @@ -169,41 +172,12 @@ } } } - &.seplevel-1 td { - .toggle { - padding-left : 25px; - } - background-color : #3d3d3d; - } - &.seplevel-2 td { - .toggle { - padding-left : 40px; - } - background-color : #383838; - } - &.seplevel-3 td { - .toggle { - padding-left : 50px; - } - background-color : #343434; - } - &.seplevel-4 td { - .toggle { - padding-left : 60px; - } - background-color : #303030; - } - &.seplevel-5 td { - .toggle { - padding-left : 70px; - } - background-color : #303030; - } - &.seplevel-6 td { + + & td { .toggle { - padding-left : 80px; + padding-left : calc(25px + var(--level) * 20px); } - background-color : #303030; + background-color: mix(#3d3d3d, #303030, calc(var(--level) / 6)); } } @@ -263,19 +237,28 @@ background-color : #f8f8f8; } - &:not(.cdb-sub-sheet) > thead.head > th { + thead.head { + background-color : #2A2A2A; + z-index: 9; + } + + &:not(.cdb-sub-sheet) > thead.head { position: sticky; - z-index: @header-layer; - top: 20px; + top: 0; + } + + .cdb-sub-sheet > thead.head { + top: 21px; + z-index: 10; + } tr.head, th { font-weight: bold; text-align: center; padding: 1px 10px 4px 10px; - box-shadow: inset 0px -1px 1px #666; - border-left: 1px solid #444; - background-color : #222; + border-left: 1px solid #666; + border-bottom: 1px solid #666; color : #ddd; width: auto; white-space: nowrap; @@ -291,9 +274,7 @@ td { user-select: none; border-left : 1px solid #444; - } - tr { border-bottom : 1px solid #333; } @@ -446,7 +427,6 @@ } td.t_flags { - position: relative; overflow: visible; } @@ -605,6 +585,12 @@ border : 1px solid black; padding : 5px; z-index : @cdb-preview-layer; + + max-width: 500px; + + >img { + width: 100%; + } } } .inlineImage { diff --git a/bin/res/icons/svg/generate.hx b/bin/res/icons/svg/generate.hx deleted file mode 100644 index 139597f9c..000000000 --- a/bin/res/icons/svg/generate.hx +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/bin/res/icons/svg/icons.css b/bin/res/icons/svg/icons.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/bin/res/icons/svg/icons.less b/bin/res/icons/svg/icons.less deleted file mode 100644 index e69de29bb..000000000 diff --git a/bin/style.css b/bin/style.css index 22deeda1a..3f64b0e3c 100644 --- a/bin/style.css +++ b/bin/style.css @@ -348,6 +348,8 @@ input[type=checkbox].indeterminate:after { max-height: 100%; overflow: auto; position: relative; + min-width: 0; + min-height: 0; } .hide-modal { position: fixed; @@ -529,6 +531,46 @@ input[type=checkbox].indeterminate:after { margin-bottom: 1px; border-radius: 3px; } +.hide-scenetree fancy-tree-item.editorOnly, +.hide-scene-layer fancy-tree-item.editorOnly { + color: #02aeda; +} +.hide-scenetree fancy-tree-item.inGameOnly, +.hide-scene-layer fancy-tree-item.inGameOnly { + color: #daa802; +} +.hide-scenetree fancy-tree-item.hidden, +.hide-scene-layer fancy-tree-item.hidden { + color: #666; +} +.hide-scenetree fancy-tree-item.hidden.selected, +.hide-scene-layer fancy-tree-item.hidden.selected { + color: #999 !important; +} +.hide-scenetree fancy-tree-item.locked, +.hide-scene-layer fancy-tree-item.locked { + font-style: italic; +} +.hide-scenetree fancy-tree-item.disabled, +.hide-scene-layer fancy-tree-item.disabled { + color: #ee6666; +} +.hide-scenetree fancy-tree-item.inRef, +.hide-scene-layer fancy-tree-item.inRef { + --background: #3a3a3a; +} +.hide-scenetree fancy-tree-item.isOverride, +.hide-scene-layer fancy-tree-item.isOverride { + --background: #0c2346; +} +.hide-scenetree fancy-tree-item.isOverride.isOverriden, +.hide-scene-layer fancy-tree-item.isOverride.isOverriden { + color: cyan; +} +.hide-scenetree fancy-tree-item.isOverride.isOverriden.isOverridenNew, +.hide-scene-layer fancy-tree-item.isOverride.isOverriden.isOverridenNew { + color: #26c726; +} .hide-range { display: inline-block; } @@ -561,6 +603,8 @@ input[type=checkbox].indeterminate:after { .flex-elt { flex: 1; display: flex; + min-height: 0; + min-width: 0; } .flex.vertical { flex-direction: column; @@ -901,6 +945,8 @@ input[type=checkbox].indeterminate:after { flex: 0 0 320px; position: relative; height: 100%; + display: flex; + flex-direction: column; } .hide-tabs.masked { flex: none; @@ -933,6 +979,7 @@ input[type=checkbox].indeterminate:after { background-color: #111; padding: 2px; padding-bottom: 0px; + flex: 0 0; } .hide-tabs .tabs-header > div { cursor: pointer; @@ -961,31 +1008,19 @@ input[type=checkbox].indeterminate:after { display: none; } .hide-tabs .tab { - max-height: 100%; - width: 100%; + flex: 1 1; + min-width: 0; + min-height: 0; display: flex; flex-direction: column; - position: absolute; } .hide-tabs .tab.expand { width: 100%; height: 100%; + overflow-y: auto; } .hide-tabs.tabs-bottom > .tabs-header { - width: 100%; - position: absolute; - bottom: 0px; - padding-top: 0px; - padding-bottom: 5px; - z-index: 600; -} -.hide-tabs.tabs-bottom > .tabs-header > div { - border-top: none; - border-top-right-radius: 0; - border-bottom-right-radius: 5px; -} -.hide-tabs.tabs-bottom > .tabs-header > div.active { - border-radius: 0px; + order: 9999; } .search-box { position: fixed; @@ -1165,6 +1200,7 @@ input[type=checkbox].indeterminate:after { .hide-properties dd { position: relative; width: calc(200px - var(--level) * 2%); + max-width: 200px; margin-left: 10px; } .hide-properties .texture-preview { @@ -2457,6 +2493,38 @@ input[type=checkbox].indeterminate:after { .graph-view .heaps-scene svg g.box foreignObject container .play-button:hover { color: #AAA; } +.graph-view .heaps-scene svg g.box .sg-const-name { + display: flex; +} +.graph-view .heaps-scene svg g.box .sg-const-name > fancy-button { + --size: 20px; + font-size: 16px; +} +.graph-view .heaps-scene svg g.box .sg-const-name > input { + width: unset; + min-width: 0; + display: block; + border: none; + background: none; + text-align: right; + margin: 0; + padding: 0; +} +.graph-view .heaps-scene svg g.box .sg-const-name > input:hover { + background-color: rgba(0, 0, 0, 0.2); +} +.graph-view .heaps-scene svg g.box .sg-const-name > input:focus { + outline: none; + background-color: rgba(0, 0, 0, 0.3); +} +.graph-view .heaps-scene svg g.box .sg-const-name > input:focus::placeholder { + color: transparent; +} +.graph-view .heaps-scene svg g.box .sg-const-input { + display: flex; + justify-content: center; + align-items: center; +} .graph-view .heaps-scene svg g.box.comment { pointer-events: none; } @@ -2768,6 +2836,49 @@ div.sp-container input:hover { display: inline-block; width: 50%; } +/* Collision Editor */ +.collision-editor .title { + display: block; + text-align: center; + font-weight: bold; + font-size: 12px; + margin: 4px; + background-color: #1e1e1e; +} +.collision-editor dl { + overflow-x: hidden; + margin: 0 0 0 4px; + width: 100%; + display: flex; +} +.collision-editor dd, +.collision-editor dt { + vertical-align: middle; + display: inline-block; + text-wrap: wrap; + word-break: break-word; + margin: 2% 5% 0 0; + flex: 2; + align-content: center; +} +.collision-editor dd select, +.collision-editor dt select { + width: 100%; +} +.collision-editor dd input, +.collision-editor dt input { + width: 100%; +} +.collision-editor dt { + text-align: right; + font-size: 11px; + color: #aaa; + user-select: none; + text-transform: capitalize; + cursor: pointer; + font-weight: normal; + flex: 1; +} /* Script Editor */ .codeeditor { position: relative; @@ -3556,6 +3667,12 @@ div.gradient-box { .project-settings { width: 100%; height: 100%; + display: flex; + flex-direction: column; +} +.project-settings, +.project-settings * { + box-sizing: border-box; } .project-settings h1 { margin: 5px 5px 5px 10px; @@ -3569,11 +3686,13 @@ div.gradient-box { align-self: center; } .project-settings .body { - width: 100%; - height: 100%; + min-height: 0; + flex-grow: 1; display: flex; } .project-settings .body .left-panel { + display: flex; + flex-direction: column; flex: 0.5; padding: 10px; } @@ -3590,11 +3709,20 @@ div.gradient-box { .project-settings .body .right-panel { flex: 1; padding: 10px; + min-height: 0; + overflow-y: auto; + scrollbar-gutter: stable; +} +.project-settings .body .array { + min-height: 0; + display: flex; + flex-direction: column; } .project-settings .body .array .rows { min-height: 20px; max-height: 750px; - overflow-y: scroll; + overflow-y: auto; + scrollbar-gutter: stable; width: 100%; background: #3f3f3f; border-radius: 2px 2px 0px 2px; @@ -3626,7 +3754,7 @@ div.gradient-box { display: none; } .project-settings .body .array .buttons { - float: right; + align-self: flex-end; padding: 5px 10px 5px 10px; border-radius: 0px 0px 2px 2px; background: #3f3f3f; @@ -4820,10 +4948,38 @@ file-browser.vertical { file-browser.single > .left { flex: 1 1; } +file-browser fancy-scroll { + display: block; + width: 100%; + flex: 1 1; + min-width: 100px; + overflow-x: clip; + overflow-y: auto; +} +file-browser fancy-scroll fancy-item-container { + position: relative; + display: block; + overflow: hidden; + contain: strict; +} file-browser .left { width: 300px; min-width: 100px; - background-color: mediumaquamarine; + background-color: #222222; + display: flex; + flex-direction: column; +} +file-browser .left .top { + width: 100%; + padding-bottom: 20px; +} +file-browser .left .top.hidden { + height: 0px; + padding-bottom: 0px; +} +file-browser .left .bot { + width: 100%; + flex: 1; } file-browser .right { flex: 1 1; @@ -4958,8 +5114,8 @@ fancy-tree-item { background-color: hsl(from var(--background) h s calc(var(--highlight) + l)); display: flex; } -fancy-tree-item.hidden { - filter: opacity(40%); +fancy-tree-item.selected { + color: white; } fancy-tree-item.hide-search { overflow: hidden; @@ -4969,37 +5125,6 @@ fancy-tree-item.hide-search { fancy-tree-item.open > .caret::before { transform: rotate(90deg); } -fancy-tree-item.selected { - color: white; - --background: var(--selection); -} -fancy-tree-item.editorOnly { - color: #02aeda; -} -fancy-tree-item.inGameOnly { - color: #daa802; -} -fancy-tree-item.hidden { - color: #555; -} -fancy-tree-item.locked { - font-style: italic; -} -fancy-tree-item.disabled { - color: #ee6666; -} -fancy-tree-item.inRef { - --background: #3a3a3a; -} -fancy-tree-item.isOverride { - --background: #0c2346; -} -fancy-tree-item.isOverride.isOverriden { - color: cyan; -} -fancy-tree-item.isOverride.isOverriden.isOverridenNew { - color: #26c726; -} fancy-tree-item:has(fancy-tree-children .selected) > fancy-tree-header fancy-tree-name { text-decoration: underline var(--selection); } @@ -5112,6 +5237,9 @@ fancy-tree-item.feedback-drop-in::after { z-index: 10; pointer-events: none; } +fancy-tree-item.selected { + --background: var(--selection) !important; +} fancy-gallery { display: flex; flex-direction: column; @@ -5199,6 +5327,14 @@ fancy-tooltip { mask-image: url("res/icons/svg/dot.svg"); color: #FE383A; } +.fancy-status-icon.fancy-status-icon-star::after { + width: 11px; + height: 11px; + left: -4px; + bottom: -1px; + mask-image: url("res/icons/svg/star.svg"); + color: #fcbf07; +} .lm_tabdropdown_list { z-index: 9999 !important; background-color: #111111; @@ -5389,3 +5525,6 @@ fancy-gallery-item.details .icon-placement { margin-left: 0.2em; font-size: 0.8em; } +.model-view-mat-not-set { + color: #ee6666 !important; +} diff --git a/bin/style.less b/bin/style.less index ec8167266..73929c8b4 100644 --- a/bin/style.less +++ b/bin/style.less @@ -374,6 +374,8 @@ input[type=checkbox] { max-height : 100%; overflow : auto; position: relative; + min-width: 0; + min-height: 0; } .hide-modal { @@ -589,6 +591,50 @@ input[type=checkbox] { border-radius: 3px; } +.hide-scenetree, .hide-scene-layer { + fancy-tree-item { + &.editorOnly { + color: rgb(2, 174, 218); + } + + &.inGameOnly { + color: rgb(218, 168, 2); + } + + &.hidden { + color: #666; + + &.selected { + color: #999 !important; + } + } + + &.locked { + font-style: italic; + } + + &.disabled { + color: #ee6666; + } + + &.inRef { + --background: rgb(58, 58, 58); + } + + &.isOverride { + --background: rgb(12, 35, 70); + } + + &.isOverride.isOverriden { + color: cyan; + } + + &.isOverride.isOverriden.isOverridenNew { + color: rgb(38, 199, 38); + } + } +} + @tileselect-cursor-border: 1px; @@ -632,6 +678,8 @@ input[type=checkbox] { .flex-elt { flex: 1; display: flex; + min-height: 0; + min-width: 0; } .flex.vertical { @@ -978,6 +1026,8 @@ input[type=checkbox] { flex : 0 0 320px; position: relative; height: 100%; + display: flex; + flex-direction: column; &.masked { & > * { @@ -1012,6 +1062,7 @@ input[type=checkbox] { background-color : #111; padding : 2px; padding-bottom : 0px; + flex: 0 0; &>div { cursor : pointer; display : inline-block; @@ -1040,34 +1091,22 @@ input[type=checkbox] { } } .tab { - max-height: 100%; - width: 100%; + flex: 1 1; + min-width: 0; + min-height: 0; display: flex; flex-direction: column; - position: absolute; &.expand { width: 100%; height: 100%; + overflow-y: auto; } } } .hide-tabs.tabs-bottom { >.tabs-header { - width:100%; - position: absolute; - bottom : 0px; - padding-top : 0px; - padding-bottom : 5px; - z-index: @header-layer; - &>div { - border-top : none; - border-top-right-radius: 0; - border-bottom-right-radius: 5px; - &.active { - border-radius: 0px; - } - } + order: 9999; } } @@ -1278,6 +1317,7 @@ input[type=checkbox] { dd { position:relative; width: calc(200px - var(--level) * 2%); + max-width: 200px; margin-left:10px; } @@ -2830,6 +2870,44 @@ input[type=checkbox] { } } + + .sg-const-name { + display: flex; + + > fancy-button { + --size: 20px; + font-size: 16px; + } + > input { + width: unset; + min-width: 0; + display: block; + border: none; + background: none; + text-align: right; + margin: 0; + padding: 0; + &:hover { + background-color: rgba(0,0,0,0.20); + + } + &:focus { + outline: none; + background-color: rgba(0,0,0,0.30); + + &::placeholder { + color: transparent; + } + } + } + } + + .sg-const-input { + display: flex; + justify-content: center; + align-items: center; + } + &.comment { .head-box { fill: rgba(80,80,80,0.5); @@ -3199,6 +3277,50 @@ div.sp-container { } } +/* Collision Editor */ +.collision-editor { + .title { + display: block; + text-align: center; + font-weight: bold; + font-size: 12px; + margin: 4px; + background-color : rgb(30,30,30); + } + dl { + overflow-x:hidden; + margin:0 0 0 4px; + width:100%; + display: flex; + } + dd, dt { + vertical-align:middle; + display:inline-block; + text-wrap: wrap; + word-break: break-word; + margin:2% 5% 0 0; + flex:2; + align-content: center; + select { + width: 100%; + } + input { + width: 100%; + } + } + dt { + text-align:right; + font-size:11px; + color:#aaa; + user-select:none; + text-transform: capitalize; + cursor:pointer; + font-weight: normal; + flex:1; + } +} + + /* Script Editor */ .codeeditor { @@ -4171,6 +4293,13 @@ div.gradient-box { width: 100%; height: 100%; + display: flex; + flex-direction: column; + + &, * { + box-sizing: border-box; + } + h1 { margin: 5px 5px 5px 10px; } @@ -4186,11 +4315,14 @@ div.gradient-box { } .body { - width: 100%; - height:100%; + min-height: 0; + flex-grow: 1; display: flex; .left-panel { + display: flex; + flex-direction: column; + flex: 0.5; padding: 10px; @@ -4211,13 +4343,21 @@ div.gradient-box { .right-panel { flex: 1; padding: 10px; + min-height: 0; + overflow-y: auto; + scrollbar-gutter: stable; } .array { + min-height: 0; + display: flex; + flex-direction: column; .rows { + min-height: 20px; max-height: 750px; - overflow-y: scroll; + overflow-y: auto; + scrollbar-gutter: stable; width: 100%; background: #3f3f3f; border-radius: 2px 2px 0px 2px; @@ -4256,7 +4396,7 @@ div.gradient-box { } .buttons { - float: right; + align-self: flex-end; padding: 5px 10px 5px 10px; border-radius: 0px 0px 2px 2px; background: #3f3f3f; @@ -5760,10 +5900,44 @@ file-browser { height: 100%; width: 100%; + fancy-scroll { + display: block; + width: 100%; + flex: 1 1; + min-width: 100px; + overflow-x: clip; + overflow-y: auto; + + fancy-item-container { + position: relative; + display: block; + overflow: hidden; + + contain: strict; + } + + } + .left { width: 300px; min-width: 100px; - background-color: mediumaquamarine; + background-color: #222222; + display: flex; + flex-direction: column; + + .top { + &.hidden { + height: 0px; + padding-bottom: 0px; + } + width: 100%; + padding-bottom: 20px; + } + + .bot { + width: 100%; + flex: 1; + } } .right { @@ -5918,8 +6092,8 @@ fancy-tree-item { color: #aaa; - &.hidden { - filter: opacity(40%); + &.selected { + color: white; } &.hide-search { @@ -5932,47 +6106,6 @@ fancy-tree-item { transform: rotate(90deg); } - &.selected { - color: white; - --background: var(--selection); - } - - &.editorOnly { - color: rgb(2, 174, 218); - } - - &.inGameOnly { - color: rgb(218, 168, 2); - } - - &.hidden { - color: #555; - } - - &.locked { - font-style: italic; - } - - &.disabled { - color: #ee6666; - } - - &.inRef { - --background: rgb(58, 58, 58); - } - - &.isOverride { - --background: rgb(12, 35, 70); - } - - &.isOverride.isOverriden { - color: cyan; - } - - &.isOverride.isOverriden.isOverridenNew { - color: rgb(38, 199, 38); - } - // Select if we have a children that is selected &:has(fancy-tree-children .selected) > fancy-tree-header { fancy-tree-name { @@ -5986,10 +6119,6 @@ fancy-tree-item { outline-offset: -1px; } - // &:hover { - // background-color: var(--hover); - // } - fancy-button.quieter { --size: 16px; font-size: 12px; @@ -6129,6 +6258,10 @@ fancy-tree-item { z-index: 10; pointer-events: none; } + + &.selected { + --background: var(--selection) !important; + } } fancy-gallery { @@ -6243,6 +6376,15 @@ fancy-tooltip { mask-image: url("res/icons/svg/dot.svg"); color: #FE383A; } + + &.fancy-status-icon-star::after { + width: 11px; + height: 11px; + left: -4px; + bottom: -1px; + mask-image: url("res/icons/svg/star.svg"); + color: #fcbf07; + } } .lm_tabdropdown_list { @@ -6476,4 +6618,8 @@ fancy-gallery-item { margin-left: 0.2em; font-size: 0.8em; } +} + +.model-view-mat-not-set { + color: #ee6666 !important; } \ No newline at end of file diff --git a/hide/Config.hx b/hide/Config.hx index 077faebf6..b3c57203b 100644 --- a/hide/Config.hx +++ b/hide/Config.hx @@ -22,7 +22,6 @@ typedef HideGlobalConfig = { var svnShowModifiedFiles : Bool; var enableDBFormulas : Bool; var screenCaptureResolution : Int; - var orientMeshOnDrag : Bool; var minDistFromCameraOnDrag : Float; // Search @@ -35,6 +34,8 @@ typedef HideGlobalConfig = { var unfocusCPUSavingMode : Bool; // Scene Editor + var orientMeshOnDrag : Bool; + var collisionOnDrag : Bool; var sceneEditorClickCycleObjects : Bool; // CDB diff --git a/hide/Ide.hx b/hide/Ide.hx index 348162b4e..058a7e70b 100644 --- a/hide/Ide.hx +++ b/hide/Ide.hx @@ -1390,6 +1390,8 @@ class Ide extends hide.tools.IdeData { function browseFiles( callb : String -> Void ) { function browseRec(path) { if( path == ".tmp" ) return; + if( path == ".backed" ) return; + for( p in sys.FileSystem.readDirectory(resourceDir + "/" + path) ) { var p = path == "" ? p : path + "/" + p; if( sys.FileSystem.isDirectory(resourceDir+"/"+p) ) { diff --git a/hide/comp/ColorPicker.hx b/hide/comp/ColorPicker.hx index 4e90f8eee..ab232d4f9 100644 --- a/hide/comp/ColorPicker.hx +++ b/hide/comp/ColorPicker.hx @@ -441,38 +441,7 @@ class ColorPicker extends Popup { } function getColorFromString(str:String) : Null { - if (str.charAt(0) == "#") - str = str.substr(1); - - var color = Std.parseInt("0x"+str); - if (color == null) - return null; - - color = color & 0xFFFFFFFF; - - var containsAlpha = false; - switch (str.length) { - case 2: // Assume color is shade of gray - color = (color << 16) + (color << 8) + (color); - case 3: // handle #XXX html codes - var r = (color >> 8) & 0xF; - var g = (color >> 4) & 0xF; - var b = (color >> 0) & 0xF; - color = (r << 20) + (r << 16) + (g << 12) + (g << 8) + (b << 4) + (b << 0); - case 6: - case 8: - containsAlpha = true; - default: - return null; - } - - if (!containsAlpha && canEditAlpha) { - color = (color) + (0xFF << 24); - } - else if (containsAlpha && !canEditAlpha) { - color = (color & 0xFFFFFF) ; - } - return color; + return Color.intFromString(str, canEditAlpha); } function get_value() { diff --git a/hide/comp/Dropdown.hx b/hide/comp/Dropdown.hx index 0b0bf01b6..5a15065d0 100644 --- a/hide/comp/Dropdown.hx +++ b/hide/comp/Dropdown.hx @@ -8,6 +8,7 @@ typedef Choice = { @:optional var classes: Array; @:optional var doc: String; @:optional var index: Int; + @:optional var searchText: String; } class Dropdown extends Component { @@ -53,6 +54,7 @@ class Dropdown extends Component { } el.data("id", o.id); el.data("text", o.text); + el.data("searchText", o.searchText); el.data("index", o.index); el.click((_) -> applyValue(o.id)); el.mousemove(function(_) { @@ -73,7 +75,7 @@ class Dropdown extends Component { var v = filterInput.val(); if (v != null) { for( o in optionsCont.children().elements() ) { - var m = matches(o.data("text"), v) || (!ignoreIdInSearch && matches(o.data("id"), v)); + var m = matches(o.data("text"), v) || (!ignoreIdInSearch && matches(o.data("id"), v)) || matches(o.data("searchText"), v); o.toggleClass("hidden", !m); } var sortedChildren = optionsCont.children().elements().toArray(); diff --git a/hide/comp/FancyTree.hx b/hide/comp/FancyTree.hx index 08113fd93..48504538c 100644 --- a/hide/comp/FancyTree.hx +++ b/hide/comp/FancyTree.hx @@ -68,7 +68,9 @@ typedef TreeButton = { typedef Params = { ?saveDisplayKey : String, - ?quickGoto : Bool + ?quickGoto : Bool, + ?search : Bool, + ?customScroll : js.html.Element } class FancyTree extends hide.comp.Component { @@ -83,6 +85,8 @@ class FancyTree extends hide.comp.Component { var gotoFileCurrentMatch = ""; var quickGoto : Bool = true; + var allowSearch : Bool = true; + var useCustomScroll : Bool = false; static final gotoFileKeyMaxDelay = 0.5; static final overDragOpenDelaySec = 0.5; @@ -118,6 +122,8 @@ class FancyTree extends hide.comp.Component { public function new(parent: Element, ?params: Params) { saveDisplayKey = params?.saveDisplayKey; quickGoto = params?.quickGoto != null ? params.quickGoto : true; + allowSearch = params?.search; + useCustomScroll = params?.customScroll != null; var el = new Element(' @@ -144,7 +150,7 @@ class FancyTree extends hide.comp.Component { onSearchClose(); } - scroll = el.find("fancy-scroll").get(0); + scroll = params?.customScroll == null ? el.find("fancy-scroll").get(0) : params.customScroll; itemContainer = el.find("fancy-item-container").get(0); lastHeight = null; @@ -221,6 +227,12 @@ class FancyTree extends hide.comp.Component { return []; } + public dynamic function onSearch() { + } + + public dynamic function onSearchEnd() { + } + /** Drag and drop interface. Set this struct with all of it's function callback to handle drag and drop inside your tree. @@ -442,10 +454,14 @@ class FancyTree extends hide.comp.Component { } function inputHandler(e: js.html.KeyboardEvent) { + if (!allowSearch) + return; + if (hide.ui.Keys.matchJsEvent("search", e, ide.currentConfig)) { e.stopPropagation(); e.preventDefault(); + onSearch(); searchBarClosable.toggleOpen(true); searchBar.focus(); currentSearch = searchBar.getValue().toLowerCase(); @@ -467,6 +483,7 @@ class FancyTree extends hide.comp.Component { e.stopPropagation(); e.preventDefault(); + onSearchEnd(); searchBarClosable.toggleOpen(false); searchBar.blur(); @@ -525,7 +542,7 @@ class FancyTree extends hide.comp.Component { return; } - if (currentItem == null || searchBar.hasFocus()) + if (currentItem == null || searchBar?.hasFocus()) return; if (e.key == "ArrowRight" && hasChildren(currentItem.item)) { @@ -628,7 +645,8 @@ class FancyTree extends hide.comp.Component { regenerateFlatData(); } - //itemContainer.innerHTML = ""; + var scrollHeight = scroll.getBoundingClientRect().height; + var offsetTop = useCustomScroll ? scroll.getElementsByClassName("top")[0].getBoundingClientRect().height : 0; var oldChildren = [for (node in itemContainer.childNodes) node]; var itemHeightPx = 20; @@ -639,13 +657,11 @@ class FancyTree extends hide.comp.Component { lastHeight = height; } - var scrollHeight = scroll.getBoundingClientRect().height; - if (currentRefreshFlags.has(FocusCurrent)) { var currentIndex = flatData.indexOf(currentItem); if (currentIndex >= 0) { - var currentHeight = currentIndex * itemHeightPx; + var currentHeight = currentIndex * itemHeightPx + offsetTop; if (currentHeight < scroll.scrollTop) { scroll.scrollTo(scroll.scrollLeft, currentHeight); } @@ -656,7 +672,7 @@ class FancyTree extends hide.comp.Component { } } - var clipStart = scroll.scrollTop; + var clipStart = scroll.scrollTop - offsetTop; var clipEnd = scrollHeight + clipStart; var itemStart = hxd.Math.floor(clipStart / itemHeightPx); var itemEnd = hxd.Math.ceil(clipEnd / itemHeightPx); diff --git a/hide/comp/GlobalSeek.hx b/hide/comp/GlobalSeek.hx index bb2245394..20fdcf734 100644 --- a/hide/comp/GlobalSeek.hx +++ b/hide/comp/GlobalSeek.hx @@ -27,16 +27,20 @@ class GlobalSeek extends Modal { } } else { function addSheet(s: cdb.Sheet) { - for (i in 0...s.all.length) { - var l = s.all[i]; + if (s.idCol == null) + return; + for (i in 0...s.lines.length) { + var l = s.lines[i]; var id = Reflect.field(l, s.idCol.name); - if (l.id != null) { - choices.push({ - id: '#${s.name}:$id', - ico: s.name, - text: l.disp, - }); - } + if (id == null) + continue; + var dispL = s.index.get(id); + choices.push({ + id: '#${s.name}:$id', + ico: s.name, + text: dispL.disp, + searchText: "" + i, + }); } } if (currentSheet != null && currentSheet.idCol != null) { diff --git a/hide/comp/Popup.hx b/hide/comp/Popup.hx index f4dbe1f36..f272d5751 100644 --- a/hide/comp/Popup.hx +++ b/hide/comp/Popup.hx @@ -9,6 +9,7 @@ class Popup extends Component { var timer : haxe.Timer; var isSearchable:Bool; public var anchor : Element; + var globalKeyListener : Dynamic = null; function onMouseDown(e : js.html.MouseEvent) { originalTarget = e.target; @@ -25,7 +26,7 @@ class Popup extends Component { var originalTarget : js.html.EventTarget; public function new(?parent:Element, isSearchable = false) { - super(parent,new Element("
")); + super(parent,new Element("
")); element.attr("popover", "").addClass("popup"); @@ -43,9 +44,6 @@ class Popup extends Component { var body = parent.closest(".lm_content"); if (body.length == 0) body = new Element("body"); - // Browser.document.addEventListener("mousedown",onMouseDown); - // Browser.document.addEventListener("mouseup", onMouseUp); - timer = new haxe.Timer(500); timer.run = function() { if( parent.closest("body").length == 0 ) { @@ -58,6 +56,24 @@ class Popup extends Component { reflow(); } + public function bindCloseOnEscape() { + globalKeyListener = (e: js.html.KeyboardEvent) -> { + // in case somehow our event wasn't properly cleaned up + if (element.closest("body") == null) { + cleanupGlobalKeyListener(); + return; + } + + if (e.key == "Escape") { + close(); + e.preventDefault(); + e.stopPropagation(); + } + }; + + Browser.document.addEventListener("keydown", globalKeyListener, true); + } + public function open() { untyped element.get(0).showPopover(); element.show(); @@ -79,9 +95,17 @@ class Popup extends Component { } element.hide(); onClose(); + if (globalKeyListener != null) { + cleanupGlobalKeyListener(); + } } } + function cleanupGlobalKeyListener() { + Browser.document.removeEventListener("keydown", globalKeyListener, true); + globalKeyListener = null; + } + function fixInputSelect() { var e = new Element("input"); e.each(function (id : Int, elem : js.html.Element) { diff --git a/hide/comp/SceneEditor.hx b/hide/comp/SceneEditor.hx index 738d2467a..58234eab1 100644 --- a/hide/comp/SceneEditor.hx +++ b/hide/comp/SceneEditor.hx @@ -2065,7 +2065,7 @@ class SceneEditor { icons.set(f, Reflect.field(iconsConfig,f)); var saveDisplayKey = isSceneTree ? view.saveDisplayKey + '/tree' : view.saveDisplayKey + '/renderPropsTree'; - var tree = new FancyTree(null, { saveDisplayKey: saveDisplayKey, quickGoto: false }); + var tree = new FancyTree(null, { saveDisplayKey: saveDisplayKey, quickGoto: false, search: true }); tree.getChildren = (p : hrt.prefab.Prefab) -> { if (p == null) { if (isSceneTree) @@ -2090,7 +2090,7 @@ class SceneEditor { var parent = p.parent; while(parent != null) { path += parent.getUniqueName() + "/" + path; - parent = parent.parent; + parent = parent.parent ?? parent.shared.parentPrefab; } return path; } @@ -2620,8 +2620,8 @@ class SceneEditor { var tag = getTag(p); if(tag != null) { - el.style.background = tag.color; - el.style.color = tag.color + "90"; + el.style.setProperty("--background", tag.color + "80"); + el.style.color = tag.color; } else { el.style.background = null; @@ -4405,59 +4405,73 @@ class SceneEditor { var camera = scene.s3d.camera; var ray = camera.rayFromScreen(scene.s2d.mouseX, scene.s2d.mouseY); - var minDist = -1.; - var hitObj : h3d.scene.Object = null; - for (obj in scene.s3d) { - function get(obj : Object) { - if (obj == previewDraggedObj || Std.isOfType(obj, hrt.tools.Gizmo)) - return; - if (!obj.getBounds().inFrustum(camera.frustum)) - return; + function snapTransform(t : h3d.Matrix) { + if (snapForceOnGrid) { + var p = t.getPosition(); + p.x = snap(p.x, snapMoveStep); + p.y = snap(p.y, snapMoveStep); + p.z = snap(p.z, snapMoveStep); + t.setPosition(p); + } - try { - var dist = obj.getCollider().rayIntersection(ray, true); - if ((minDist < 0 || (dist >= 0 && dist < minDist)) && dist > ide.ideConfig.minDistFromCameraOnDrag) { - minDist = dist; - hitObj = obj; + return t; + } + + if (ide.ideConfig.collisionOnDrag) { + var minDist = -1.; + var hitObj : h3d.scene.Object = null; + for (obj in scene.s3d) { + function get(obj : Object) { + if (obj == previewDraggedObj || Std.isOfType(obj, hrt.tools.Gizmo)) + return; + if (!obj.getBounds().inFrustum(camera.frustum)) + return; + + try { + var dist = obj.getCollider().rayIntersection(ray, true); + if ((minDist < 0 || (dist >= 0 && dist < minDist)) && dist > ide.ideConfig.minDistFromCameraOnDrag) { + minDist = dist; + hitObj = obj; + } } + catch (e : Dynamic) {}; + + for (c in @:privateAccess obj.children) + get(c); } - catch (e : Dynamic) {}; - for (c in @:privateAccess obj.children) - get(c); + get(obj); } - get(obj); - } + if (minDist >= 0) { + // Find hit normal + var center = ray.getPoint(minDist); + var dx : h3d.col.Point = null; + var dy : h3d.col.Point = null; + var ray2 = camera.rayFromScreen(scene.s2d.mouseX + 1, scene.s2d.mouseY); + dx = ray2.getPoint(hitObj.getCollider().rayIntersection(ray2, true)); - if (minDist >= 0) { - // Find hit normal - var center = ray.getPoint(minDist); - var dx : h3d.col.Point = null; - var dy : h3d.col.Point = null; - var ray2 = camera.rayFromScreen(scene.s2d.mouseX + 1, scene.s2d.mouseY); - dx = ray2.getPoint(hitObj.getCollider().rayIntersection(ray2, true)); + var ray3 = camera.rayFromScreen(scene.s2d.mouseX - 1, scene.s2d.mouseY + 1); + dy = ray3.getPoint(hitObj.getCollider().rayIntersection(ray3, true)); - var ray3 = camera.rayFromScreen(scene.s2d.mouseX - 1, scene.s2d.mouseY + 1); - dy = ray3.getPoint(hitObj.getCollider().rayIntersection(ray3, true)); + var ddx = dx - center; + var ddy = dy - center; + var norm = ddx.cross(ddy); + norm.normalize(); - var ddx = dx - center; - var ddy = dy - center; - var norm = ddx.cross(ddy); - norm.normalize(); + if (ide.ideConfig.orientMeshOnDrag) { + var q = new h3d.Quat(); + q.initMoveTo(new h3d.Vector(0, 0, 1), norm); + transform = q.toMatrix(); + } + else { + transform = new h3d.Matrix(); + transform.identity(); + } - if (ide.ideConfig.orientMeshOnDrag) { - var q = new h3d.Quat(); - q.initMoveTo(new h3d.Vector(0, 0, 1), norm); - transform = q.toMatrix(); + transform.setPosition(center); + return snapTransform(transform); } - else { - transform = new h3d.Matrix(); - transform.identity(); - } - - transform.setPosition(center); - return transform; } // If there is no collision with objects, try to collide with z=0 plane @@ -4465,10 +4479,10 @@ class SceneEditor { var pt = ray.intersect(zPlane); if (pt != null) { transform.setPosition(pt); - return transform; + return snapTransform(transform); } - transform.setPosition(ray.getPoint(minDist)); + transform.setPosition(ray.getPoint(10)); return transform; } diff --git a/hide/comp/cdb/Cell.hx b/hide/comp/cdb/Cell.hx index d19a403a1..b8f044a79 100644 --- a/hide/comp/cdb/Cell.hx +++ b/hide/comp/cdb/Cell.hx @@ -1105,6 +1105,8 @@ class Cell { elementHtml.innerHTML = null; var modal = new hide.comp.Popup(new Element(elementHtml)); + modal.bindCloseOnEscape(); + var div = modal.element; div.addClass("flagValues"); div.click(function(e) e.stopPropagation()).dblclick(function(e) e.stopPropagation()); @@ -1132,7 +1134,7 @@ class Cell { } modal.onClose = () -> { setValue(val); - closeEdit(); + closeEdit(false); }; @:privateAccess modal.reflow(); case TTilePos: @@ -1248,7 +1250,7 @@ class Cell { var e = new Element(elementHtml); e.addClass("edit"); if (editor.gradientEditor == null) { - editor.gradientEditor = new GradientEditor(editor.element.parents(".hide-scroll").first(), false); + editor.gradientEditor = new GradientEditor(editor.element, false); editor.gradientEditor.keepPreviewAlive = true; } editor.gradientEditor.anchor = e; @@ -1316,6 +1318,7 @@ class Cell { var e = new Element(elementHtml); e.addClass("edit"); var curveEditor = new hide.comp.CurveEditor.CurvePopup(e, editor.undo); + curveEditor.bindCloseOnEscape(); var prefabCurve = new hrt.prefab.Curve(null, null); var linear : Float = cast hrt.prefab.Curve.CurveKeyMode.Linear; @@ -1459,7 +1462,7 @@ class Cell { editor.changeObject(line,column,value); } - public function closeEdit() { + public function closeEdit(refreshFocus: Bool = true) { inEdit = false; var input = new Element(elementHtml).find("div[contenteditable]").get(0); var text : String = input?.innerText; @@ -1469,7 +1472,9 @@ class Cell { } refresh(); - focus(); + if (refreshFocus) { + focus(); + } } public function editCustomType(typeName : String, ctValue : Dynamic, col : cdb.Data.Column, parentEl : Element, rightAnchor: Float, topAnchor : Float, depth : Int = 0) { diff --git a/hide/comp/cdb/Cursor.hx b/hide/comp/cdb/Cursor.hx index 7f4aa0527..a954da58a 100644 --- a/hide/comp/cdb/Cursor.hx +++ b/hide/comp/cdb/Cursor.hx @@ -379,7 +379,13 @@ class Cursor { public function getLinesFromSelection(sel : Selection) { if (sel == null || sel.x1 >= 0) return null; - return [for( iy in sel.y1...(sel.y2 + 1) ) table.lines[iy]]; + var array : Array = []; + for( iy in sel.y1...(sel.y2 + 1) ) { + var line = table.lines[iy]; + if (!line.element.hasClass("filtered")) + array.push(line); + } + return array; } public function getCellsFromSelection(sel : Selection) { @@ -389,15 +395,21 @@ class Cursor { var cells = []; if (sel.x1 == -1) { for (y in sel.y1...(sel.y2 + 1)) { - for (x in 0...(table.lines[y].cells.length)) { - cells.push(table.lines[y].cells[x]); + var line = table.lines[y]; + if (line.element.hasClass("filtered")) + continue; + for (x in 0...(line.cells.length)) { + cells.push(line.cells[x]); } } } else { for (y in sel.y1...(sel.y2 + 1)) { + var line = table.lines[y]; + if (line.element.hasClass("filtered")) + continue; for (x in sel.x1...(sel.x2 + 1)) { - cells.push(table.lines[y].cells[x]); + cells.push(line.cells[x]); } } } diff --git a/hide/comp/cdb/Editor.hx b/hide/comp/cdb/Editor.hx index b1eaa73dd..37b67c0f1 100644 --- a/hide/comp/cdb/Editor.hx +++ b/hide/comp/cdb/Editor.hx @@ -27,6 +27,7 @@ typedef UndoState = { var separatorsState: Map; var cursor : Cursor.CursorState; var tables : Array; + var sheetOrder : Array; } typedef EditorApi = { @@ -189,7 +190,8 @@ class Editor extends Component { keys.register("cdb.globalSeekIds", () -> new GlobalSeek(cdbTable.element, cdbTable, GlobalIds, currentSheet)); base = sheet.base; - cursor = new Cursor(this); + if( cursor == null ) + cursor = new Cursor(this); if( displayMode == null ) displayMode = Table; DataFiles.load(); @@ -236,14 +238,13 @@ class Editor extends Component { cursor.move( e.shiftKey ? -1 : 1, 0, false, false, true); return true; case K.PGUP: - var scrollView = element.parent(".hide-scroll"); - var stickyElHeight = scrollView.find(".separator").height(); + var stickyElHeight = element.find(".separator").height(); if (Math.isNaN(stickyElHeight)) - stickyElHeight = scrollView.find("thead").outerHeight(); + stickyElHeight = element.find("thead").outerHeight(); else - stickyElHeight += scrollView.find("thead").outerHeight(); + stickyElHeight += element.find("thead").outerHeight(); - var lines = scrollView.find("tbody").find(".start"); + var lines = element.find("tbody").find(".start"); var idx = lines.length - 1; while (idx >= 0) { var b = lines[idx].getBoundingClientRect(); @@ -256,13 +257,12 @@ class Editor extends Component { lines.get(idx).scrollIntoView({ block: js.html.ScrollLogicalPosition.END }); // Handle sticky elements - scrollView.scrollTop(scrollView.scrollTop() + scrollView.parent().siblings(".tabs-header").outerHeight()); + element.scrollTop(element.scrollTop() + element.parent().siblings(".tabs-header").outerHeight()); return true; case K.PGDOWN: - var scrollView = element.parent(".hide-scroll"); - var height = scrollView.outerHeight() - (scrollView.find("thead").outerHeight() + scrollView.parent().siblings(".tabs-header").outerHeight()); - var lines = scrollView.find("tbody").find(".start"); + var height = element.outerHeight() - (element.find("thead").outerHeight() + element.parent().siblings(".tabs-header").outerHeight()); + var lines = element.find("tbody").find(".start"); var idx = 0; for (el in lines) { var b = el.getBoundingClientRect(); @@ -277,10 +277,10 @@ class Editor extends Component { cursor.setDefault(cursor.table, cursor.x, idx); // Handle sticky elements - var sepHeight = scrollView.find(".separator").height(); + var sepHeight = element.find(".separator").height(); if (Math.isNaN(sepHeight)) sepHeight = 0; - scrollView.scrollTop(scrollView.scrollTop() - (scrollView.find("thead").height() + sepHeight)); + element.scrollTop(element.scrollTop() - (element.find("thead").height() + sepHeight)); return true; case K.SPACE: @@ -319,10 +319,8 @@ class Editor extends Component { if (filters.length == 0 && filterFlags.has(Regular) && filterFlags.has(Warning) && filterFlags.has(Error)) return; - for (s in @:privateAccess table.separators) { + for (s in @:privateAccess table.separators) @:privateAccess s.filtered = true; - s.refresh(false); - } // Clean filters var idx = filters.length; @@ -347,7 +345,7 @@ class Editor extends Component { var results = 0; for (l in table.lines) { var filtered = isLineFilteredBySearch(table, l); - if (filtered) + if (!filtered) results++; filtered = filtered || isLineFilteredByStatus(l); @@ -375,7 +373,6 @@ class Editor extends Component { searchBox.find("#results").text(results > 0 ? '$results Results' : 'No results'); - // if (updateCursor) cursor.update(); } @@ -567,7 +564,7 @@ class Editor extends Component { } function onCopy() { - if( cursor.selection == null ) + if( cursor.selection == null || cursor.table == null ) return; function saveValue(out: Dynamic, obj: Dynamic, c: cdb.Data.Column) { @@ -1003,7 +1000,7 @@ class Editor extends Component { **/ public function beginChanges( ?structure : Bool ) { if (undoState.length >= @:privateAccess undo.maxHistoryCount) - undoState.shift(); + undoState.pop(); if( changesDepth == 0 ) undoState.unshift(getState()); changesDepth++; @@ -1026,6 +1023,7 @@ class Editor extends Component { } makeParent(tables[i]); }], + sheetOrder : cdbTable?.sheetsOrder.copy(), }; } @@ -1081,6 +1079,7 @@ class Editor extends Component { var state = undoState[0]; var newSheet = getCurrentSheet(); var newSaparatorsState = separatorsState; + var newSheetsOrder = cdbTable?.sheetsOrder.copy(); currentValue = newValue; save(); undo.change(Custom(function(undo) { @@ -1090,11 +1089,13 @@ class Editor extends Component { currentValue = state.data; currentSheet = state.sheet; separatorsState = state.separatorsState; + if( cdbTable != null ) cdbTable.sheetsOrder = state.sheetOrder; } else { undoState.unshift(state); currentValue = newValue; currentSheet = newSheet; separatorsState = newSaparatorsState; + if( cdbTable != null ) cdbTable.sheetsOrder = newSheetsOrder; } api.load(currentValue); DataFiles.save(true); // save reloaded data @@ -2415,6 +2416,9 @@ class Editor extends Component { for( s in base.sheets ) if( StringTools.startsWith(s.name, old + "@") ) s.rename(name + "@" + s.name.substr(old.length + 1)); + var order = cdbTable.sheetsOrder.indexOf(old); + if( order >= 0 ) + cdbTable.sheetsOrder[order] = name; endChanges(); DataFiles.save(true,[ sheet.name => old ]); return true; @@ -2464,6 +2468,18 @@ class Editor extends Component { return null; } ide.saveDatabase(); + var orderIndex = -1; + if( index == 0 ) { + orderIndex = 0; + } else if( index > 0 ) { + var prevSheetName = base.sheets[index-1]?.name; + orderIndex = prevSheetName == null ? -1 : cdbTable.sheetsOrder.findIndex((s) -> s == prevSheetName); + if( orderIndex >= 0 ) + orderIndex += 1; + } + if( orderIndex >= 0 && orderIndex <= cdbTable.sheetsOrder.length ) { + cdbTable.sheetsOrder.insert(orderIndex, s.name); + } refreshAll(); return s; } @@ -2479,8 +2495,8 @@ class Editor extends Component { if (withMacro) { content = content.concat([ { label : "Add Sheet", click : function() { beginChanges(); var db = createDBSheet(index+1); endChanges(); if( db != null ) onChange(); } }, - { label : "Move Left", click : function() { beginChanges(); base.moveSheet(sheet,-1); endChanges(); onChange(); } }, - { label : "Move Right", click : function() { beginChanges(); base.moveSheet(sheet,1); endChanges(); onChange(); } }, + { label : "Move Left", click : function() { beginChanges(); cdbTable.moveSheetDisplayOrder(sheet,-1); endChanges(); onChange(); } }, + { label : "Move Right", click : function() { beginChanges(); cdbTable.moveSheetDisplayOrder(sheet,1); endChanges(); onChange(); } }, { label : "Rename", click : function() { var name = ide.ask("New name", sheet.name); if( name == null || name == "" || name == sheet.name ) return; diff --git a/hide/comp/cdb/Formulas.hx b/hide/comp/cdb/Formulas.hx index 754942892..7a8870329 100644 --- a/hide/comp/cdb/Formulas.hx +++ b/hide/comp/cdb/Formulas.hx @@ -179,29 +179,34 @@ class Formulas { if( s.props.hasIndex ) m.index = s.lines.indexOf(o); if( s.props.hasGroup ) { - var gid = -1; var sindex = 0; var sheet = s; + var last = "None"; // skip separators if at head while( true ) { var s = sheet.separators[sindex]; if( s == null || s.index != 0 ) break; sindex++; - if( s.title != null ) gid++; + if( s.title != null ) + last = s.title; } - if( gid < 0 ) gid++; // None insert var index = s.lines.indexOf(o); for( i in sindex...sheet.separators.length ) { var s = sheet.separators[i]; if( s.index > index ) break; - if( s.title != null ) gid++; + if( s.title != null ) + last = s.title; } - m.group = gid; + m.group = toIdent(last); } return m; } + public static function toIdent( name : String ) { + return ~/[^A-Za-z0-9_]/g.replace(name,"_"); + } + function load() { var code = try sys.io.File.getContent(ide.getPath(formulasFile)) catch( e : Dynamic ) return; var parser = new hscript.Parser(); @@ -209,16 +214,25 @@ class Formulas { var expr = try parser.parseString(code) catch( e : Dynamic ) return; var sheetNames = new Map(); - for( s in editor.base.sheets ) - sheetNames.set(getTypeName(s), s); + var enumNames = new Map(); + for( s in editor.base.sheets ) { + var name = getTypeName(s); + sheetNames.set(name, s); + if( s.props.hasGroup ) + enumNames.set(name+"_group", true); + for( c in s.columns ) { + switch( c.type ) { + case TEnum(_): + enumNames.set(name+"_"+c.name, true); + default: + } + } + } var changed = false; var refs : Array = []; function replaceRec( e : hscript.Expr ) { switch( e.e ) { - case EField({ e : EIdent(s) }, name) if ( s == "Sheets" ): - e.e = EIdent(name); - changed = true; case EField({ e : EIdent(s) }, name) if( sheetNames.exists(s) ): if( name == "all" || name == "resolve" ) { var found = false; @@ -229,6 +243,8 @@ class Formulas { refs.push(new SheetAccess(this, s)); } else if( sheetNames.get(s).idCol != null ) e.e = EConst(CString(name)); // replace for faster eval + case EField({ e : EIdent(s) }, name) if( enumNames.exists(s) ): + e.e = EConst(CString(name)); // replace for faster eval default: hscript.Tools.iter(e, replaceRec); } @@ -246,6 +262,8 @@ class Formulas { var o : Dynamic = { Math : Math, Ok : ValidationResult.Ok, Error : ValidationResult.Error, Warning : ValidationResult.Warning }; for( r in refs ) Reflect.setField(o,r.name, r); + + hscript.JsInterp.defineArrayExtensions(); var interp = new hscript.JsInterp(); interp.ctx = o; interp.properties = ["all" => true]; @@ -433,8 +451,8 @@ class FormulasView extends hide.view.Script { } var carray = switch( _tarray ) { case TInst(c,_): c; default: throw "assert"; } function tarray(t) return TInst(carray,[t]); + function mkType(name,t) return TType({name:name,params:[],t:t},[]); - var sfields : Array<{name : String, t : TType, opt : Bool}> = []; var cdefs = new Map(); for( s in ide.database.sheets ) { var cdef : CClass = { @@ -444,25 +462,40 @@ class FormulasView extends hide.view.Script { params : [], }; cdefs.set(s.name, cdef); + if( s.getParent() != null ) + continue; var afields = [ { name : "all", t : tarray(TInst(cdef,[])), opt : false, - }, - { + } + ]; + if( s.idCol != null ) { + var tkind = skind.get(s.name); + afields.push({ name : "resolve", t : TFun([{t:tstring,name:"id",opt:false}],TInst(cdef,[])), opt : false, + }); + for( v in s.getLines() ) { + var id = Reflect.field(v, s.idCol.name); + if( id != null && id != "" ) + afields.push({ name : id, t : tkind, opt : false }); } - ]; + } + var t = mkType("#"+cdef.name,TAnon(afields)); + check.checker.setGlobal(cdef.name, t); + } - sfields.push({name: cdef.name, t : TAnon(afields), opt : false}); - check.checker.setGlobal(cdef.name, TAnon(afields)); + function defineEnum(name,values:Array) { + values = [for( v in values ) Formulas.toIdent(v)]; + var t = TEnum({ name : name, params : [], constructors : [for( v in values ) {name:v}] },[]); + var tvalues = [for( v in values ) {name:v,t:t,opt:false}]; + check.checker.setGlobal(name,mkType("#"+name,TAnon(tvalues))); + return t; } - check.checker.setGlobal("Sheets", TAnon(sfields)); - var tenum = TInst(check.checker.types.defineClass("EnumValue"),[]); for( s in ide.database.sheets ) { var cdef = cdefs.get(s.name); inline function addField(name,t) { @@ -473,9 +506,7 @@ class FormulasView extends hide.view.Script { case TId: skind.get(s.name); case TInt, TColor: TInt; case TEnum(values): - for( v in values ) - check.checker.setGlobal(v,tenum); - tenum; + defineEnum(cdef.name+"_"+c.name, values); case TFlags(flags): TAnon([for(f in flags) { name : f, t : TBool, opt : true }]); case TFloat: TFloat; @@ -492,8 +523,17 @@ class FormulasView extends hide.view.Script { if( t == null ) continue; addField(c.name,t); } - if( s.props.hasGroup ) - addField("group",TInt); + if( s.props.hasGroup ) { + var groups = []; + for( s in s.separators ) { + if( s.level == null && s.title != null ) { + if( s.index != 0 && groups.length == 0 ) groups.push("None"); + groups.push(s.title); + } + } + var t = defineEnum(cdef.name+"_group",groups); + addField("group",t); + } if( s.props.hasIndex ) addField("index",TInt); check.checker.types.defineClass(cdef.name, cdef); diff --git a/hide/comp/cdb/Separator.hx b/hide/comp/cdb/Separator.hx index 5b8f3d044..1b123809c 100644 --- a/hide/comp/cdb/Separator.hx +++ b/hide/comp/cdb/Separator.hx @@ -238,8 +238,7 @@ class Separator extends Component { } element.attr("level", data.level == null ? 0 : data.level); - element.removeClass("seplevel-"+(data.level == null ? 0 : data.level)); - element.addClass('seplevel-'+(data.level == null ? 0 : data.level)); + element.get(0).style.setProperty("--level", data.level == null ? "0" : ""+data.level); if (refreshChildren) { for (s in subs) diff --git a/hide/comp/cdb/Table.hx b/hide/comp/cdb/Table.hx index 984e59bae..cd8649478 100644 --- a/hide/comp/cdb/Table.hx +++ b/hide/comp/cdb/Table.hx @@ -123,13 +123,6 @@ class Table extends Component { function setupTableElement() { cloneTableHead(); - #if js - @:privateAccess { - var elt = editor.element.parent(); - var scrollbarWidth = elt.parent().width() - elt.width(); - element.width(@:privateAccess editor.cdbTable.contentWidth - scrollbarWidth); // prevent to reflow all cdb-view - } - #end } function cloneTableHead() { diff --git a/hide/tools/FileManager.hx b/hide/tools/FileManager.hx index 6a9afab1c..fc91ba87b 100644 --- a/hide/tools/FileManager.hx +++ b/hide/tools/FileManager.hx @@ -600,12 +600,12 @@ class FileManager { queueProcessPendingMessages(); } - public static function doRename(path:String, name:String) { - var isDir = sys.FileSystem.isDirectory(hide.Ide.inst.getPath(path)); - if( isDir ) hide.Ide.inst.fileWatcher.pause(); - var ret = onRenameRec(path, name); - if( isDir ) hide.Ide.inst.fileWatcher.resume(); - return ret; + public static function doRename(operations: Array<{from: String, to: String}>) { + for (op in operations) { + onRenameRec(op.from, "/" + op.to); + } + + replacePathInFiles(operations); } public static function onRenameRec(path:String, name:String) { @@ -623,6 +623,9 @@ class FileManager { if( newPath == path ) return false; + if (!sys.FileSystem.exists(ide.getPath(path))) + return false; + if( sys.FileSystem.exists(ide.getPath(newPath)) ) { function addPath(path:String,rand:String) { var p = path.split("."); @@ -679,8 +682,6 @@ class FileManager { if( !wasRenamed ) sys.FileSystem.rename(ide.getPath(path), ide.getPath(newPath)); - replacePathInFiles(path, newPath, isDir); - var dataDir = new haxe.io.Path(path); if( dataDir.ext != "dat" ) { dataDir.ext = "dat"; @@ -782,20 +783,23 @@ class FileManager { return true; } - public static function replacePathInFiles(oldPath: String, newPath: String, isDir: Bool = false) { + public static function replacePathInFiles(operations: Array<{from: String, to: String}>) { function filter(ctx: hide.Ide.FilterPathContext) { var p = ctx.valueCurrent; if( p == null ) return; - if( p == oldPath ) { - ctx.change(newPath); - return; - } - if( p == "/"+oldPath ) { - ctx.change(newPath); - return; - } - if( isDir ) { + for (op in operations) { + var oldPath = op.from; + var newPath = op.to; + + if( p == oldPath ) { + ctx.change(newPath); + return; + } + if( p == "/"+oldPath ) { + ctx.change(newPath); + return; + } if( StringTools.startsWith(p,oldPath+"/") ) { ctx.change(newPath + p.substr(oldPath.length)); return; diff --git a/hide/tools/IdeData.hx b/hide/tools/IdeData.hx index 0bc50c212..163b364ec 100644 --- a/hide/tools/IdeData.hx +++ b/hide/tools/IdeData.hx @@ -29,6 +29,7 @@ class IdeData { "screenCaptureResolution" => 4096, "sceneEditorClickCycleObjects" => true, "orientMeshOnDrag" => true, + "collisionOnDrag" => true, "minDistFromCameraOnDrag" => 1, ]; diff --git a/hide/tools/ThumbnailGenerator.hx b/hide/tools/ThumbnailGenerator.hx index 26c317749..0e393bb51 100644 --- a/hide/tools/ThumbnailGenerator.hx +++ b/hide/tools/ThumbnailGenerator.hx @@ -195,7 +195,7 @@ class ThumbnailGenerator { toHash += getRenderProps(filePath, config); toHash += sys.FileSystem.stat(filePath).mtime.getTime(); if (filePath.split(".").pop().toLowerCase() == "fbx") { - var matInfo = getMaterialInfo(filePath); + var matInfo = try getMaterialInfo(filePath) catch(e) "__error__"; toHash += matInfo; } return haxe.crypto.Md5.encode(toHash); diff --git a/hide/view/CdbTable.hx b/hide/view/CdbTable.hx index aa47648db..7c349f335 100644 --- a/hide/view/CdbTable.hx +++ b/hide/view/CdbTable.hx @@ -1,13 +1,17 @@ package hide.view; +using hide.tools.Extensions; + class CdbTable extends hide.ui.View<{}> { + public var sheetsOrder : Array; var tabContents : Array; var editor : hide.comp.cdb.Editor; var currentSheet : String; var tabCache : String; var tabs : hide.comp.Tabs; var view : cdb.DiffFile.ConfigView; + var topBar : Element; public function new( ?state ) { super(state); @@ -22,6 +26,9 @@ class CdbTable extends hide.ui.View<{}> { undoStack[0] = editor.undo; currentSheet = this.config.get("cdb.currentSheet"); view = cast this.config.get("cdb.view"); + saveDisplayKey = "cdb:" + ide.getPath(@:privateAccess ide.databaseFile); + sheetsOrder = getDisplayState("sheetsOrder"); + if( sheetsOrder == null ) sheetsOrder = []; } override function destroy() { @@ -44,8 +51,8 @@ class CdbTable extends hide.ui.View<{}> { return; } - if (tabs.currentTab.get(0) != tabContents[index].parent().get(0)) - tabs.currentTab = tabContents[index].parent(); + if (tabs.currentTab.get(0) != tabContents[index].get(0)) + tabs.currentTab = tabContents[index]; @:privateAccess editor.filters = []; @:privateAccess editor.updateFilters(); @@ -151,7 +158,7 @@ class CdbTable extends hide.ui.View<{}> { return; } - tabs.currentTab = tabContents[index].parent(); + tabs.currentTab = tabContents[index]; @:privateAccess editor.filters = []; @:privateAccess editor.updateFilters(); if( line != null ) { @@ -184,7 +191,26 @@ class CdbTable extends hide.ui.View<{}> { } public function getSheets() { - return [for( s in ide.database.sheets ) if( !s.props.hide && (view == null || view.exists(s.name)) ) s]; + var arr = [for( s in ide.database.sheets ) if( !s.props.hide && (view == null || view.exists(s.name)) ) s]; + haxe.ds.ArraySort.sort(arr, (s1, s2) -> sheetsOrder.indexOf(s1.name) - sheetsOrder.indexOf(s2.name)); + sheetsOrder = arr.map(s -> s.name); + saveSheetsOrder(); + return arr; + } + + public function saveSheetsOrder() { + saveDisplayState("sheetsOrder", sheetsOrder); + } + + public function moveSheetDisplayOrder( s : cdb.Sheet, delta : Int ) { + var index = sheetsOrder.findIndex(o -> o == s.name); + var newIndex = index + delta; + if( index < 0 || newIndex < 0 || newIndex >= sheetsOrder.length ) + return false; + var order = sheetsOrder[index]; + sheetsOrder.remove(order); + sheetsOrder.insert(newIndex, order); + return true; } function getTabCache() { @@ -202,6 +228,14 @@ class CdbTable extends hide.ui.View<{}> { editor.show(sheets[index],tabContents[index]); currentSheet = editor.getCurrentSheet(); ide.currentConfig.set("cdb.currentSheet", sheets[index].name); + + var validationFunc = @:privateAccess editor.formulas?.validationFuncs?.get(currentSheet); + if (validationFunc == null) { + topBar.get(0).style.display = "none"; + } else { + topBar.get(0).style.display = null; + } + haxe.Timer.delay(editor.focus,1); } @@ -225,13 +259,13 @@ class CdbTable extends hide.ui.View<{}> { tabContents = []; for( sheet in sheets ) { var tab = tabs == null ? element : tabs.createTab(sheet.name); - var sc = new hide.comp.Scrollable(tab); - tabContents.push(sc.element); + tabContents.push(tab); } if( tabs != null ) { tabs.onTabChange = setEditor; tabs.onTabRightClick = function(index) { - editor.popupSheet(true, getSheets()[index], function() { + var sheet = getSheets()[index]; + editor.popupSheet(true, sheet, function() { var newSheets = getSheets(); var delta = newSheets.length - sheets.length; var sshow = null; @@ -240,7 +274,7 @@ class CdbTable extends hide.ui.View<{}> { else if( delta < 0 ) sshow = newSheets[index-1]; else - sshow = newSheets[index]; // rename + sshow = sheet; // rename or move display order if( sshow != null ) currentSheet = sshow.name; if( getTabCache() != tabCache ) @@ -250,12 +284,12 @@ class CdbTable extends hide.ui.View<{}> { }; } - var topBar = new Element('
+ topBar = new Element('

0

0

0

'); - topBar.prependTo(element.find(".hide-scroll")); + tabs.element.prepend(topBar); function filterListener(el : Element, flag : hide.comp.cdb.Editor.FilterFlag) { var disabled = el.hasClass("disabled"); @@ -276,6 +310,7 @@ class CdbTable extends hide.ui.View<{}> { var errorEl = element.find(".error"); errorEl.on("click", function(e) { filterListener(errorEl, Error); }); + if( sheets.length > 0 ) { var idx = 0; for( i in 0...sheets.length ) @@ -283,7 +318,7 @@ class CdbTable extends hide.ui.View<{}> { idx = i; break; } - tabs.currentTab = tabContents[idx].parent(); + tabs.currentTab = tabContents[idx]; } applyCategories(ide.projectConfig.dbCategories, false); diff --git a/hide/view/FileBrowser.hx b/hide/view/FileBrowser.hx index 54e6a2e23..cdd7d65ac 100644 --- a/hide/view/FileBrowser.hx +++ b/hide/view/FileBrowser.hx @@ -2,6 +2,13 @@ package hide.view; import hide.tools.FileManager; import hide.tools.FileManager.FileEntry; + +typedef FavoriteEntry = { + parent : FavoriteEntry, + children : Array, + ?ref : FileEntry, +} + typedef FileBrowserState = { savedLayout: Layout, } @@ -14,6 +21,7 @@ enum abstract Layout(String) { } class FileBrowser extends hide.ui.View { + static var FAVORITES_KEY = "filebrowser_favorites"; var fileTree: Element; var fileIcons: Element; @@ -46,6 +54,10 @@ class FileBrowser extends hide.ui.View { override function new(state) { super(state); saveDisplayKey = "fileBrowser"; + + this.favorites = getDisplayState(FAVORITES_KEY); + if (this.favorites == null) + this.favorites = []; } override function buildTabMenu():Array { @@ -134,6 +146,9 @@ class FileBrowser extends hide.ui.View { var statFileCount: Int = 0; var statFileFiltered: Int = 0; + var favorites : Array; + var favoritesTree : hide.comp.FancyTree; + function set_filterEnabled(v : Bool) { var anySet = false; for (key => value in filterState) { @@ -355,7 +370,12 @@ class FileBrowser extends hide.ui.View { var browserLayout = new Element(' -
+
+ +
+
+
+
@@ -413,7 +433,241 @@ class FileBrowser extends hide.ui.View { } } - fancyTree = new hide.comp.FancyTree(browserLayout.find(".left"), { saveDisplayKey: "fileBrowserTree" } ); + // Favorites tree + favoritesTree = new hide.comp.FancyTree(browserLayout.find(".left").find(".top"), { saveDisplayKey: "fileBrowserTree_Favorites" } ); + favoritesTree.getChildren = (file: FavoriteEntry) -> { + function rec(parent : FavoriteEntry) { + if (parent.ref.children == null) + return; + for (c in parent.ref.children) { + var f = { parent : parent, children : [], ref: FileManager.inst.getFileEntry(c.path) }; + parent.children.push(f); + rec(f); + } + } + + if (file == null) { + var fav : FavoriteEntry = { + parent : null, + children : [], + ref : null + } + + fav.children = []; + for (f in favorites) { + // Ref could be null if this f is a favorite of another project + var ref = FileManager.inst.getFileEntry(f); + if (ref == null) + continue; + var c = { parent: fav, children: [], ref: ref } + fav.children.push(c); + rec(c); + } + + return [fav]; + } + + if (file?.ref?.kind == File) + return null; + if (file.children == null) + throw "null children"; + + return file.children; + + // return [for (c in file.children) { parent: file, children: [], ref: FileManager.inst.getFileEntry(c.ref.path) }]; + }; + favoritesTree.getName = (file: FavoriteEntry) -> return file.ref == null ? "Favorites" : file?.ref.name; + favoritesTree.getUniqueName = (file: FavoriteEntry) -> { + if (file == null) + return ""; + if (file.ref == null) + return "favorites"; + var relPath = file.ref.name; + var p = file.parent; + while (p != null) { + var name = p.ref == null ? "favorites" : p.ref.name; + relPath = name + "/" + relPath; + p = p.parent; + } + return relPath; + } + favoritesTree.getIcon = (file: FavoriteEntry) -> { + if (file.parent == null) + return '
'; + + var fav = file.parent.ref == null ? "fancy-status-icon fancy-status-icon-star" : ""; + if (file.ref.kind == Dir) + return '
'; + var ext = Extension.getExtension(file.ref.name); + if (ext != null) { + if (ext?.options.icon != null) { + return '
'; + } + } + return '
'; + }; + favoritesTree.onContextMenu = (item: FavoriteEntry, event: js.html.MouseEvent) -> { + event.stopPropagation(); + event.preventDefault(); + + var options : Array = []; + options.push({ + label: "Collapse", + click: () -> { + var collapseTarget = item; + if (item.ref.kind != Dir) + collapseTarget = item.parent; + favoritesTree.collapseItem(collapseTarget); + } + }); + options.push({ + label: "Collapse All", + click: () -> { + for (child in @:privateAccess favoritesTree.rootData) + favoritesTree.collapseItem(child.item); + } + }); + options.push({ + isSeparator: true + }); + + // Root favorite tree options + var isFavoriteRoot = item?.parent == null; + if (isFavoriteRoot) { + options.push({ + label: "Clear Favorites", + click: () -> { + favorites = []; + saveDisplayState(FAVORITES_KEY, favorites); + favoritesTree.rebuildTree(); + this.favoritesTree.element.parent().hide(); + } + }); + + hide.comp.ContextMenu.createFromEvent(event, options); + return; + } + else { + if (!this.favorites.contains(item.ref.getPath())) { + options.push({ label: "Mark as Favorite", click : function() { + this.favorites.push(item.ref.getPath()); + saveDisplayState(FAVORITES_KEY, this.favorites); + this.favoritesTree.rebuildTree(); + this.favoritesTree.element.parent().show(); + }}); + } + else { + options.push({ label: "Remove from Favorite", click : function() { + this.favorites.remove(item.ref.getPath()); + saveDisplayState(FAVORITES_KEY, this.favorites); + this.favoritesTree.rebuildTree(); + if (this.favorites.length == 0) + this.favoritesTree.element.parent().hide(); + }}); + } + } + + hide.comp.ContextMenu.createFromEvent(event, options); + }; + favoritesTree.onDoubleClick = (item: FavoriteEntry) -> { + if (item?.ref?.kind == File) + ide.openFile(item.ref.getPath()); + else + favoritesTree.openItem(item); + } + favoritesTree.onSelectionChanged = (enterKey) -> { + fancyTree.clearSelection(); + + var selection = favoritesTree.getSelectedItems(); + + // Sinc folder view with other filebrowser in SingleMiniature mode + if (selection.length > 0) { + if (selection[0].ref == null) return; + + openDir(selection[0].ref, false); + var views = ide.getViews(hide.view.FileBrowser); + for (view in views) { + if (view == this) + continue; + if (view.layout == SingleMiniature) { + view.openDir(selection[0].ref, false); + } + } + } + + if (enterKey) { + if (selection[0].ref.kind == File) { + ide.openFile(selection[0].ref.getPath()); + } + } + } + favoritesTree.dragAndDropInterface = { + onDragStart: function(file: FavoriteEntry, e: hide.tools.DragAndDrop.DragData) : Bool { + var selection = favoritesTree.getSelectedItems(); + if (selection.length <= 0) + return false; + + var fileEntries = []; + for (s in selection) { + if (s.ref != null) + fileEntries.push(s.ref); + } + + e.data.set("drag/filetree", fileEntries); + ide.setData("drag/filetree", cast fileEntries); + return true; + }, + getItemDropFlags: function(target: FavoriteEntry, e: hide.tools.DragAndDrop.DragData) : hide.comp.FancyTree.DropFlags { + if (target == null) + return Reorder; + + var fileEntries : Array = cast e.data.get("drag/filetree") ?? []; + fileEntries = fileEntries.copy(); + var containsFiles = fileEntries != null && fileEntries.length > 0; + + if (!containsFiles) + return hide.comp.FancyTree.DropFlags.ofInt(0); + + // Can't drop a file on itself + fileEntries.remove(target.ref); + + if (fileEntries.length == 0) + return hide.comp.FancyTree.DropFlags.ofInt(0); + + if (target.ref.kind == Dir) + return (Reorder:hide.comp.FancyTree.DropFlags) | Reparent; + + return Reorder; + }, + onDrop: function(target: FavoriteEntry, operation: hide.comp.FancyTree.DropOperation, e: hide.tools.DragAndDrop.DragData) : Bool { + if (target.ref.kind != Dir) + target = target.parent; + + var fileEntries : Array = cast e.data.get("drag/filetree") ?? []; + fileEntries = fileEntries.copy(); + fileEntries.remove(target.ref); + + var files = [ for (f in fileEntries) f.path ]; + if (files.length == 0) + return false; + + if(!ide.confirm('Really move files :\n${files.join("\n")}\nto target folder :\n${target.ref.getRelPath()}\n?\n(This could take a long time)')) { + return true; + } + + moveFiles(target.ref.getRelPath(), files); + + return true; + } + } + + favoritesTree.rebuildTree(); + + if (this.favorites.length == 0) + this.favoritesTree.element.parent().hide(); + + // Ressources tree + fancyTree = new hide.comp.FancyTree(browserLayout.find(".left").find(".bot"), { saveDisplayKey: "fileBrowserTree_Main", search: true, customScroll: element.find("fancy-scroll").get(0) } ); fancyTree.getChildren = (file: FileEntry) -> { if (file == null) return [root]; @@ -437,6 +691,15 @@ class FileBrowser extends hide.ui.View { fancyTree.onNameChange = renameHandler; + fancyTree.onSearch = () -> { + favoritesTree.element.parent().hide(); + } + + fancyTree.onSearchEnd = () -> { + if (this.favorites.length > 0) + favoritesTree.element.parent().show(); + } + fancyTree.dragAndDropInterface = { onDragStart: function(file: FileEntry, e: hide.tools.DragAndDrop.DragData) : Bool { @@ -526,7 +789,6 @@ class FileBrowser extends hide.ui.View { fancyGallery.getThumbnail = (item : FileEntry) -> { if (item.kind == Dir) { return ''; - } else if (item.iconPath == "loading") { return ''; @@ -585,6 +847,8 @@ class FileBrowser extends hide.ui.View { fancyTree.onSelectionChanged = (enterKey) -> { + favoritesTree.clearSelection(); + var selection = fancyTree.getSelectedItems(); // Sinc folder view with other filebrowser in SingleMiniature mode @@ -701,27 +965,13 @@ class FileBrowser extends hide.ui.View { static function execMoveFiles(operations: Array<{from: String, to: String}>, isUndo: Bool) : Void { if (!isUndo) { - for (file in operations) { - // File could have been removed by the system in between our undo/redo operations - if (sys.FileSystem.exists(hide.Ide.inst.getPath(file.from))) { - try { - FileManager.doRename(file.from, "/" + file.to); - } catch (e) { - hide.Ide.inst.quickError('move file ${file.from} -> ${file.to} failed : $e'); - } - } - } + FileManager.doRename(operations); } else { - for (file in operations) { - // File could have been removed by the system in between our undo/redo operations - if (sys.FileSystem.exists(hide.Ide.inst.getPath(file.to))) { - try { - FileManager.doRename(file.to, "/" + file.from); - } catch (e) { - hide.Ide.inst.quickError('move file ${file.from} -> ${file.to} failed : $e'); - } - } + var rev = []; + for (op in operations) { + rev.push({from: op.to, to: op.from}); } + FileManager.doRename(rev); } } @@ -986,17 +1236,34 @@ class FileBrowser extends hide.ui.View { options.push({ label: "Replace Refs With", click : function() { ide.chooseFile(["*"], (newPath: String) -> { - var selection = [for (file in getItemAndSelection(item, isGallery)) file.getRelPath()]; - if(ide.confirm('Replace all refs of $selection with $newPath ? This action can not be undone')) { - for (oldPath in selection) { - FileManager.replacePathInFiles(oldPath, newPath, false); - } - ide.message("Done"); + var files = [for (file in getItemAndSelection(item, isGallery)) file.getRelPath()]; + if(ide.confirm('Replace all refs of $files with $newPath ? This action can not be undone')) { + var selection = [for (file in files) {from: file, to: newPath}]; + FileManager.replacePathInFiles(selection); + ide.message("All references replaced"); } }); }}); } + if (!this.favorites.contains(item.getPath())) { + options.push({ label: "Mark as Favorite", click : function() { + this.favorites.push(item.getPath()); + saveDisplayState(FAVORITES_KEY, this.favorites); + this.favoritesTree.rebuildTree(); + this.favoritesTree.element.parent().show(); + }}); + } + else { + options.push({ label: "Remove from Favorite", click : function() { + this.favorites.remove(item.getPath()); + saveDisplayState(FAVORITES_KEY, this.favorites); + this.favoritesTree.rebuildTree(); + if (this.favorites.length == 0) + this.favoritesTree.element.parent().hide(); + }}); + } + options.push({ label: "Refresh Thumbnail(s)", click : function() { var files = FileManager.inst.getRoots(getItemAndSelection(item, isGallery)); for (file in files) { diff --git a/hide/view/Image.hx b/hide/view/Image.hx index 54370adb4..58105877e 100644 --- a/hide/view/Image.hx +++ b/hide/view/Image.hx @@ -491,7 +491,7 @@ class Image extends FileView { } var compTexMemSize = element.find(".comp-tex-weight"); - compTexMemSize.text('Compressed texture weight : ${@:privateAccess floatToStringPrecision(compressedTexture.mem.memSize(compressedTexture) / (1024 * 1024)) } mb'); + compTexMemSize.text('Compressed texture weight : ${@:privateAccess floatToStringPrecision(compressedTexture.mem.memSize(compressedTexture) / (1024 * 1024)) } MB'); updateImageCompressionInfos(); applyShaderConfiguration(); @@ -659,7 +659,7 @@ class Image extends FileView { size.val(texMaxSize); var uncompTWeight = element.find(".uncomp-tex-weight"); - uncompTWeight.text('Uncompressed texture weight : ${getTextureMemSize(state.path)} mb'); + uncompTWeight.text('Uncompressed texture weight : ${getTextureMemSize(state.path)} MB'); nativeFormat.text(getTextureNativeFormat(state.path).getName()); } @@ -716,7 +716,7 @@ class Image extends FileView { } var compTexMemSize = element.find(".comp-tex-weight"); - compTexMemSize.text('Compressed texture weight : ${@:privateAccess floatToStringPrecision(t.mem.memSize(t) / (1024 * 1024)) } mb'); + compTexMemSize.text('Compressed texture weight : ${@:privateAccess floatToStringPrecision(t.mem.memSize(t) / (1024 * 1024)) } MB'); applyShaderConfiguration(); onResize(); diff --git a/hide/view/Model.hx b/hide/view/Model.hx index eec394d77..e8ca8fb22 100644 --- a/hide/view/Model.hx +++ b/hide/view/Model.hx @@ -1,6 +1,24 @@ package hide.view; import hxd.Key as K; +typedef CollisionSettings = { mode : Int, params : Dynamic }; + +private enum abstract CollisionMode(Int) from Int to Int { + var Default = 0; + var None = 1; + var Auto = 2; + var Count = 3; + + public function toString() { + return switch (this) { + case Default: "Default"; + case None: "None"; + case Auto: "Auto"; + default: "Undefined"; + } + } +} + class Model extends FileView { static var KEY_ANIM_PLAYING = "AnimationPlaying"; @@ -11,6 +29,8 @@ class Model extends FileView { var tabs : hide.comp.Tabs; var overlay : Element; var eventList : Element; + var collisionEditor : Element; + var collisionSettings : Map; var plight : hrt.prefab.Prefab; var light : h3d.scene.Object; @@ -70,8 +90,25 @@ class Model extends FileView {
+
+
+ +
+
Collision mode
+
+ +
+
+
+
Precision
+
Max convex hulls
+
max subdivision
+
+
+
-
'); @@ -80,6 +117,7 @@ class Model extends FileView { overlay = element.find(".hide-scene-layer .tree"); tabs = new hide.comp.Tabs(null,element.find(".tabs")); eventList = element.find(".event-editor"); + initCollisionEditor(); var def = new hrt.prefab.Prefab(null, null); new hrt.prefab.RenderProps(def, null).name = "renderer"; @@ -146,6 +184,75 @@ class Model extends FileView { @:privateAccess hxd.fmt.hmd.Library.defaultDynamicBonesConfigs.clear(); } + function updateCollisionParams(settings) { + var collisionParams = element.find("#collision-params"); + switch (settings.mode) { + case Default, None: collisionParams.hide(); + case Auto: + collisionParams.show(); + + var elPrec = collisionParams.find("#precision"); + var elHull = collisionParams.find("#hulls"); + var elSubdiv = collisionParams.find("#subdiv"); + + var params = settings.params; + elPrec.val('${params.precision}'); + elHull.val('${params.maxConvexHulls}'); + elSubdiv.val('${params.maxSubdiv}'); + default: throw "Unexpected mode"; + } + } + + function initCollisionEditor() { + collisionEditor = element.find(".collision-editor"); + collisionEditor.hide(); + collisionSettings = []; + + var collisionParams = collisionEditor.find("#collision-params"); + var elPrec = collisionParams.find("#precision"); + var elHull = collisionParams.find("#hulls"); + var elSubdiv = collisionParams.find("#subdiv"); + collisionParams.on("change", function(_) { + var modelName = selectedMesh.name; + var settings = collisionSettings.get(modelName); + var prevParams = settings.params; + var curParams = { + precision : Std.parseFloat(elPrec.val()), + maxConvexHulls : Std.parseInt(elHull.val()), + maxSubdiv : Std.parseInt(elSubdiv.val()) + } + settings.params = curParams; + undo.change(Custom(function(undo) { + var params = undo ? prevParams : curParams; + settings.params = params; + if ( modelName == selectedMesh.name ) { + elPrec.val('${params.precision}'); + elHull.val('${params.maxConvexHulls}'); + elSubdiv.val('${params.maxSubdiv}'); + } + })); + }); + + var collisionDropdown = collisionEditor.find("#select-collision-mode"); + collisionDropdown.on("change", function(_) { + var modelName = selectedMesh.name; + var settings = collisionSettings.get(modelName); + var prevMode = settings.mode; + var curMode = Std.parseInt(collisionDropdown.val()); + settings.mode = curMode; + updateCollisionParams(settings); + + undo.change(Custom(function(undo) { + var mode = undo ? prevMode : curMode; + settings.mode = mode; + if ( modelName == selectedMesh.name ) { + collisionDropdown.val('${Std.int(mode)}'); + updateCollisionParams(settings); + } + })); + }); + } + override function onActivate() { if (tools != null) tools.refreshToggles(); @@ -157,6 +264,7 @@ class Model extends FileView { if(!modified) return; + var needRefresh = false; // Save render props if (Ide.inst.currentConfig.get("sceneeditor.renderprops.edit", false) && sceneEditor.renderPropsRoot != null) sceneEditor.renderPropsRoot.save(); @@ -167,12 +275,23 @@ class Model extends FileView { if (hmd == null) continue; + var collide = {}; + var settings = collisionSettings.get(o.name); + if ( settings != null ) { + switch ( settings.mode ) { + case Default: + case None: Reflect.setField(collide, "collide", null); + case Auto: Reflect.setField(collide, "collide", settings.params); + default: throw "Unexpected collision mode"; + } + } var input : h3d.prim.ModelDatabase.ModelDataInput = { resourceDirectory : @:privateAccess hmd.lib.resource.entry.directory, resourceName : @:privateAccess hmd.lib.resource.name, objectName : o.name, hmd : hmd, - skin : o.find((o) -> Std.downcast(o, h3d.scene.Skin)) + skin : o.find((o) -> Std.downcast(o, h3d.scene.Skin)), + collide : collide } // If Dynamic bones edition scope is global or folder, save it to props.json @@ -204,6 +323,11 @@ class Model extends FileView { } else { h3d.prim.ModelDatabase.current.saveModelProps(input); + var lfs = cast(hxd.res.Loader.currentInstance.fs, hxd.fs.LocalFileSystem); + var path = state.path; + lfs.removePathFromCache(path); + @:privateAccess hxd.res.Loader.currentInstance.cache.remove(path); + needRefresh = true; } } @@ -225,6 +349,7 @@ class Model extends FileView { } super.save(); + if(needRefresh) onRefresh(); } override function onFileChanged( wasDeleted : Bool, rebuildView = true ) { @@ -286,6 +411,8 @@ class Model extends FileView { refreshSelectionHighlight(null); selectedElements = elts; + collisionEditor.hide(); + var properties = sceneEditor.properties; properties.clear(); @@ -469,7 +596,7 @@ class Model extends FileView {
'); properties.add(blendShape, null, function(pname){ - @:privateAccess hmd.blendshape.setBlendshapeAmountByIndex(blendShape.find("#bs-index").val(),blendShape.find("#bs-amount").val()); + @:privateAccess hmd.blendshape.setBlendShapeWeight(mesh, blendShape.find("#bs-index").val(), blendShape.find("#bs-amount").val()); }); } @@ -626,6 +753,31 @@ class Model extends FileView { }); } } + + // Collision edition + var modelName = mesh.name; + + var settings = collisionSettings.get(modelName); + if ( settings == null ) { + var mode = Default; + var dirPath = @:privateAccess hmd.lib.resource.entry.directory; + var resName = @:privateAccess hmd.lib.resource.name; + var props = @:privateAccess h3d.prim.ModelDatabase.current.getModelData(dirPath, resName, modelName); + var collideField = null; + if ( props != null && Reflect.hasField(props, "collide") ) { + collideField = Reflect.field(props, "collide"); + mode = collideField == null ? None : Auto; + } + var params = collideField ?? { precision : 1.0, maxConvexHulls : 1, maxSubdiv : 32 }; + settings = { mode : mode, params : params }; + collisionSettings.set(modelName, settings); + } + + var collisionDropdown = collisionEditor.find("#select-collision-mode"); + collisionDropdown.val('${Std.int(settings.mode)}'); + updateCollisionParams(settings); + + collisionEditor.show(); } var select = e.find(".follow"); @@ -902,6 +1054,7 @@ class Model extends FileView { } h3d.mat.MaterialSetup.current.saveMaterialProps(m, defaultProps); Ide.inst.quickMessage('Properties for mat (${m.name}) had been saved'); + tree.queueRefresh(RegenHeader); }; saveButton.click(saveCallback); properties.add(matLibrary, m); @@ -1548,6 +1701,16 @@ class Model extends FileView { tree.rebuildTree(); tree.openItem(obj, true); + tree.applyStyle = (item: Dynamic, elt : js.html.Element) -> { + if (hide.Ide.inst.currentConfig.get("highlightUnsetMats") ?? false) { + var mat = Std.downcast(item, h3d.mat.Material); + if (mat != null) { + var props : Dynamic = h3d.mat.MaterialSetup.current.loadMaterialProps(mat); + elt.classList.toggle("model-view-mat-not-set", props == null || props.__ref == null); + } + } + } + tools.clear(); var anims = scene.listAnims(getPath()); @@ -2166,7 +2329,8 @@ class Model extends FileView { obj.playAnimation(anim); var skin = Std.downcast(obj, h3d.scene.Skin); - @:privateAccess skin.prevAbsPosFrame = -1; + if (skin != null) + @:privateAccess skin.prevAbsPosFrame = -1; buildTimeline(); buildEventPanel(); modified = false; diff --git a/hide/view/settings/UserSettings.hx b/hide/view/settings/UserSettings.hx index 66a698603..de183bf22 100644 --- a/hide/view/settings/UserSettings.hx +++ b/hide/view/settings/UserSettings.hx @@ -10,7 +10,6 @@ class UserSettings extends Settings { general.add("Show versioned files in filetree", new Element(''), Ide.inst.ideConfig.svnShowVersionedFiles, (v) -> {Ide.inst.ideConfig.svnShowVersionedFiles = v; for(view in Ide.inst.getViews(FileBrowser)) view.refreshVCS(); }); general.add("Show modified files in filetree", new Element(''), Ide.inst.ideConfig.svnShowModifiedFiles, (v) -> {Ide.inst.ideConfig.svnShowModifiedFiles = v; for(view in Ide.inst.getViews(FileBrowser)) view.refreshVCS(); }); general.add("Screen capture resolution", new Element(''), Ide.inst.ideConfig.screenCaptureResolution, (v) -> {Ide.inst.ideConfig.screenCaptureResolution = v; }); - general.add("Orient mesh on drag", new Element(''), Ide.inst.ideConfig.orientMeshOnDrag, (v) -> {Ide.inst.ideConfig.orientMeshOnDrag = v; }); general.add("Minimal distance from camera on drag", new Element(''), Ide.inst.ideConfig.minDistFromCameraOnDrag, (v) -> {Ide.inst.ideConfig.minDistFromCameraOnDrag = v; }); categories.push(general); @@ -28,6 +27,8 @@ class UserSettings extends Settings { categories.push(performance); var sceneEditor = new hide.view.settings.Settings.Categorie("Scene Editor"); + sceneEditor.add("Use objects collision on drag", new Element(''), Ide.inst.ideConfig.collisionOnDrag, (v) -> {Ide.inst.ideConfig.collisionOnDrag = v; }); + sceneEditor.add("Orient mesh on drag", new Element(''), Ide.inst.ideConfig.orientMeshOnDrag, (v) -> {Ide.inst.ideConfig.orientMeshOnDrag = v; }); sceneEditor.add("Click cycle objects under the mouse", new Element(''), Ide.inst.ideConfig.sceneEditorClickCycleObjects, (v) -> Ide.inst.ideConfig.sceneEditorClickCycleObjects = v); categories.push(sceneEditor); diff --git a/hide/view/shadereditor/Box.hx b/hide/view/shadereditor/Box.hx index 9409d96db..0184d58eb 100644 --- a/hide/view/shadereditor/Box.hx +++ b/hide/view/shadereditor/Box.hx @@ -383,10 +383,12 @@ class Box { var prop = editor.editorDisplay.group(propertiesGroup).addClass("prop-group"); prop.attr("transform", 'translate(0, ${propsHeight})'); + var height = p.height(); var propWidth = (p.width() > 0 ? p.width() : this.width); - var fObject = editor.editorDisplay.foreignObject(prop, (this.width - propWidth) / 2, 5, propWidth, p.height()); + var fObject = editor.editorDisplay.foreignObject(prop, (this.width - propWidth) / 2, 5, propWidth, height); + trace(p.height()); p.appendTo(fObject); - propsHeight += Std.int(p.outerHeight()) + 1; + propsHeight += Std.int(height) + 1; } propsHeight += 10; @@ -481,7 +483,7 @@ class Box { } var nodeHeight = getNodesHeight(); if (collapseProperties()) { - return hxd.Math.max(nodeHeight, propsHeight); + return hxd.Math.max(nodeHeight, propsHeight + HEADER_HEIGHT); } return nodeHeight + propsHeight; } diff --git a/hrt/impl/ColorSpace.hx b/hrt/impl/ColorSpace.hx index 8780ba13c..b14d122ef 100644 --- a/hrt/impl/ColorSpace.hx +++ b/hrt/impl/ColorSpace.hx @@ -23,18 +23,66 @@ class Color { } inline static public function fromInt(rgb: Int, withAlpha: Bool = true) : Color { - return new Color( - (rgb >> 16) & 0xFF, - (rgb >> 8) & 0xFF, - (rgb >> 0) & 0xFF, - withAlpha ? (rgb >> 24) & 0xFF : 255 - ); + var c = new Color(); + c.load(rgb, withAlpha); + return c; + } + + public function load(rgb: Int, withAlpha: Bool = true) { + r = (rgb >> 16) & 0xFF; + g = (rgb >> 8) & 0xFF; + b = (rgb >> 0) & 0xFF; + if (withAlpha) { + a = (rgb >> 24) & 0xFF; + } else { + a = 255; + } } inline public function toInt(includeAlpha: Bool) : Int { return if (!includeAlpha) (r << 16) + (g << 8) + b else (r << 16) + (g << 8) + (b << 0) + (a << 24); } + + static public function intFromString(str:String, withAlpha: Bool) : Null { + if (str.charAt(0) == "#") + str = str.substr(1); + + var color = Std.parseInt("0x"+str); + if (color == null) + return null; + + color = color & 0xFFFFFFFF; + + var containsAlpha = false; + switch (str.length) { + case 2: // Assume color is shade of gray + color = (color << 16) + (color << 8) + (color); + case 3: // handle #XXX html codes + var r = (color >> 8) & 0xF; + var g = (color >> 4) & 0xF; + var b = (color >> 0) & 0xF; + color = (r << 20) + (r << 16) + (g << 12) + (g << 8) + (b << 4) + (b << 0); + case 6: + case 8: + containsAlpha = true; + default: + return null; + } + + if (!containsAlpha && withAlpha) { + color = (color) + (0xFF << 24); + } + else if (containsAlpha && !withAlpha) { + color = (color & 0xFFFFFF) ; + } + return color; + } + + function toString() : String { + return '[$r, $g, $b, $a]'; + } + } class ColorSpace { diff --git a/hrt/impl/EditorTools.hx b/hrt/impl/EditorTools.hx index 5cd270bcc..f55f9dd3b 100644 --- a/hrt/impl/EditorTools.hx +++ b/hrt/impl/EditorTools.hx @@ -8,6 +8,7 @@ class EditorIcon extends hrt.prefab.l3d.Billboard.BillboardObj { super(tile, parent); ignoreCollide = false; this.category = category; + this.material.castShadows = false; } override function sync(ctx) { diff --git a/hrt/prefab/Light.hx b/hrt/prefab/Light.hx index 4d5f128ac..ba001d942 100644 --- a/hrt/prefab/Light.hx +++ b/hrt/prefab/Light.hx @@ -153,7 +153,6 @@ class Light extends Object3D { o.y = y; o.z = z; o.setRotation(hxd.Math.degToRad(rotationX), hxd.Math.degToRad(rotationY), hxd.Math.degToRad(rotationZ)); - } function initTexture( path : String, ?wrap : h3d.mat.Data.Wrap ) { @@ -210,6 +209,13 @@ class Light extends Object3D { if( light != null ) { // PBR light.isMainLight = isMainLight; light.occlusionFactor = occlusionFactor; + light.color.setColor(color); + light.power = power; + light.shadows.mode = shadows.mode; + light.shadows.size = shadows.size; + light.shadows.blur.radius = shadows.radius; + light.shadows.blur.quality = shadows.quality; + light.shadows.bias = shadows.bias * 0.1; switch( kind ) { case Directional: @@ -223,13 +229,15 @@ class Light extends Object3D { var cs = Std.downcast(s, h3d.pass.CascadeShadowMap); if ( cs != null ) { cs.cascade = cascadeNbr; - cs.pow = cascadePow; + cs.ditributionPower = cascadePow; cs.firstCascadeSize = firstCascadeSize; cs.minPixelSize = minPixelSize; cs.debug = debugDisplay; cs.castingMaxDist = castingMaxDist; cs.transitionFraction = transitionFraction; cs.debugShader = debugShader; + cs.blur.radius = 0.0; + cs.mode = shadows.mode == None ? None : Dynamic; params.resize(cascadeNbr); for ( i in 0...params.length ) if ( params[i] == null ) @@ -265,13 +273,6 @@ class Light extends Object3D { rec.fallOff = fallOff; default: } - light.color.setColor(color); - light.power = power; - light.shadows.mode = shadows.mode; - light.shadows.size = shadows.size; - light.shadows.blur.radius = shadows.radius; - light.shadows.blur.quality = shadows.quality; - light.shadows.bias = shadows.bias * 0.1; switch (shadows.samplingMode.kind) { case None: @@ -687,9 +688,9 @@ class Light extends Object3D { -
Blur Radius
-
Blur Quality
-
+
+
Blur Radius
+
Blur Quality
Bias
Sampling Mode
@@ -704,11 +705,11 @@ class Light extends Object3D {
'); - var biasEl = shadowGroup.find(".bias"); + var nonCascadeParamsEl = shadowGroup.find(".nonCascadeParams"); if ( cascade ) - biasEl.hide(); + nonCascadeParamsEl.hide(); else - biasEl.show(); + nonCascadeParamsEl.show(); switch (shadows.samplingMode.kind) { case None: @@ -740,7 +741,7 @@ class Light extends Object3D {
Number
Min pixel size
First cascade size
-
Range power
+
Distribution power
Casting max dist
Transition fraction
diff --git a/hrt/prefab/Material.hx b/hrt/prefab/Material.hx index 2a39eddb7..3a365fc28 100644 --- a/hrt/prefab/Material.hx +++ b/hrt/prefab/Material.hx @@ -254,6 +254,7 @@ class Material extends Prefab { var propsToApply = currMat.renderProps(); propsToApply = applyOverrides(propsToApply); + currMat.materialName = materialName; currMat.applyTo(local3d, propsToApply, shared); } @@ -652,7 +653,7 @@ class Material extends Prefab { var dropDownMaterials = new hide.Element('
-
Name
+
Target material
'); var select = dropDownMaterials.find("select"); var materialList = findFirstLocal3d()?.getMaterials(); @@ -675,7 +676,7 @@ class Material extends Prefab { })); ctx.onChange(this, null); ctx.rebuildProperties(); - ctx.scene.editor.queueRebuild(this); + ctx.scene.editor.queueRebuild(this.parent); var fx = findParent(hrt.prefab.fx.FX); if(fx != null) diff --git a/hrt/prefab/Shader.hx b/hrt/prefab/Shader.hx index 79d59548a..666f3ec2f 100644 --- a/hrt/prefab/Shader.hx +++ b/hrt/prefab/Shader.hx @@ -258,6 +258,14 @@ class Shader extends Prefab { ectx.properties.add(propGroup, this, function(pname) { if( targetMaterial == "" ) targetMaterial = null; ectx.onChange(this, pname); + + var o3d = this.findParent(Object3D); + if (o3d != null) + ectx.rebuildPrefab(o3d); + + var o2d = this.findParent(Object2D); + if (o2d != null) + ectx.rebuildPrefab(o2d); }); var group = new hide.Element('
'); diff --git a/hrt/prefab/fx/gpuemitter/BaseSimulation.hx b/hrt/prefab/fx/gpuemitter/BaseSimulation.hx index bb1af293a..cd3329390 100644 --- a/hrt/prefab/fx/gpuemitter/BaseSimulation.hx +++ b/hrt/prefab/fx/gpuemitter/BaseSimulation.hx @@ -44,7 +44,7 @@ class BaseSimulation extends ComputeUtils { lifeTime = particleBuffer[computeVar.globalInvocation.x].lifeTime; prevModelView = batchBuffer[computeVar.globalInvocation.x].modelView; particleRandom = particleBuffer[computeVar.globalInvocation.x].random; - particleColor = int2rgba(floatBitsToInt(particleBuffer[computeVar.globalInvocation.x].color)); + particleColor = unpackIntColor(floatBitsToInt(particleBuffer[computeVar.globalInvocation.x].color)); relativeTransform = scaleMatrix(((INFINITE || life < lifeTime) ? 1.0 : 0.0) * (computeVar.globalInvocation.x > curCount ? 0.0 : 1.0) * vec3(particleRandom * (maxSize - minSize) + minSize)); } @@ -62,7 +62,7 @@ class BaseSimulation extends ComputeUtils { var idx = computeVar.globalInvocation.x; particleBuffer[idx].life = life + dt; particleBuffer[idx].speed = speed; - particleBuffer[idx].color = intBitsToFloat(rgba2int(particleColor)); + particleBuffer[idx].color = intBitsToFloat(packIntColor(particleColor)); batchBuffer[idx].modelView = modelView; } } diff --git a/hrt/prefab/fx/gpuemitter/BaseSpawn.hx b/hrt/prefab/fx/gpuemitter/BaseSpawn.hx index ce0a436d7..a0776ca21 100644 --- a/hrt/prefab/fx/gpuemitter/BaseSpawn.hx +++ b/hrt/prefab/fx/gpuemitter/BaseSpawn.hx @@ -56,7 +56,7 @@ class BaseSpawn extends ComputeUtils { particleBuffer[idx].life = 0.0; // Keep in memory duration of particle to normalize curve update. particleBuffer[idx].lifeTime = life; - particleBuffer[idx].color = intBitsToFloat(rgba2int(particleColor)); + particleBuffer[idx].color = intBitsToFloat(packIntColor(particleColor)); } } } diff --git a/hrt/prefab/fx/gpuemitter/GPUEmitterObject.hx b/hrt/prefab/fx/gpuemitter/GPUEmitterObject.hx index 5ab14ca57..8ad46e34b 100644 --- a/hrt/prefab/fx/gpuemitter/GPUEmitterObject.hx +++ b/hrt/prefab/fx/gpuemitter/GPUEmitterObject.hx @@ -29,7 +29,7 @@ class ParticleShader extends hxsl.Shader { particleLife = particleBuffer[instanceID].life; particleLifeTime = particleBuffer[instanceID].lifeTime; particleRandom = particleBuffer[instanceID].random; - particleColor = int2rgba(floatBitsToInt(particleBuffer[instanceID].color)); + particleColor = unpackIntColor(floatBitsToInt(particleBuffer[instanceID].color)); } transformedPosition = transformedPosition * absPos.mat3x4(); } diff --git a/hrt/prefab/fx/gpuemitter/RandomColorSpawn.hx b/hrt/prefab/fx/gpuemitter/RandomColorSpawn.hx index b88a6ca4c..21ba9dc85 100644 --- a/hrt/prefab/fx/gpuemitter/RandomColorSpawn.hx +++ b/hrt/prefab/fx/gpuemitter/RandomColorSpawn.hx @@ -12,8 +12,8 @@ class RandomColorSpawnShader extends ComputeUtils { var particleColor : Vec4; function main() { var idx = computeVar.globalInvocation.x; - var fromColor = int2rgba(color1); - var toColor = int2rgba(color2); + var fromColor = unpackIntColor(color1); + var toColor = unpackIntColor(color2); particleColor = mix(fromColor, toColor, particleRandom); } } diff --git a/hrt/prefab/l3d/LocalVolumetricLighting.hx b/hrt/prefab/l3d/LocalVolumetricLighting.hx new file mode 100644 index 000000000..05f59177e --- /dev/null +++ b/hrt/prefab/l3d/LocalVolumetricLighting.hx @@ -0,0 +1,259 @@ +package hrt.prefab.l3d; + +class LocalVolumetricShader extends hxsl.Shader { + + static var SRC = { + + final EPSILON = 1e-4; + final FLT_MAX = 3.402823466e+38; + + @global var depthMap : Channel; + + @global var camera : { + var position : Vec3; + var inverseViewProj : Mat4; + }; + + @global var global : { + @perObject var modelView : Mat4; + @perObject var modelViewInverse : Mat4; + }; + + @param var obH : Vec3; + + @param var fogColor : Vec4; + @param var fogDensity : Float; + @param var fogFade : Float; + + var screenUV : Vec2; + var transformedPosition : Vec3; + var transformedNormal : Vec3; + var pixelColor : Vec4; + + function maxComp(a : Vec3) : Float { return max(a.x, max(a.y, a.z)); } + function minComp(a : Vec3) : Float { return min(a.x, min(a.y, a.z)); } + + function getPositionAt( uv: Vec2 ) : Vec3 { + var depth = depthMap.get(uv); + var uv2 = uvToScreen(uv); + var temp = vec4(uv2, depth, 1) * camera.inverseViewProj; + var originWS = temp.xyz / temp.w; + return originWS; + } + + function getPosition() : Vec3 { + return getPositionAt(screenUV); + } + + function rayBoxIntersection( o : Vec3, d : Vec3) : Vec3 { + var m = 1.0/d; + var n = m*o; + var k = abs(m)*obH; + var t1 = -n - k; + var t2 = -n + k; + var tN = maxComp(t1); + var tF = minComp(t2); + var hit = vec3(tN, tF, 1.0); + if(tN>tF || tF<0.0) { + hit = vec3(-1.0, -1.0, -1.0); + } + return hit; + } + + function getPath(o : Vec3, d : Vec3) : Vec4 { + var dir = normalize(d); + var hit = rayBoxIntersection(o, dir); + var path = vec4(o, -1.0); + if(hit.z > 0.0){ + if(hit.x > 0.0){ + path = vec4(o+hit.x*dir, hit.y - hit.x); + } else { + path.w = hit.y; + } + } + + var backgroundLocalPosition = (vec4(getPosition(), 1.0) * global.modelViewInverse).xyz; + var backgroundDist = length(backgroundLocalPosition-path.xyz); + path.w = min(path.w, backgroundDist); + return path; + } + + function boxDistance(p : Vec3) : Float { + var d = abs(p) - obH; + return length(max(d,0.0)) + min(maxComp(d),0.0); + } + + function boxDensity( o : Vec3, d : Vec3, t : Float) : Float{ + var ir2 = 1.0/(obH*obH); + var a = 1.0 - (o*o)*ir2; + var b = - 2.0*(o*d)*ir2; + var c = - (d*d)*ir2; + + var t1 = t; + var t2 = t1*t1; + var t3 = t2*t1; + var t4 = t2*t2; + var t5 = t2*t3; + var t6 = t3*t3; + var t7 = t3*t4; + + var f = (t1/1.0) *(a.x*a.y*a.z) + + (t2/2.0) *(a.x*a.y*b.z + a.x*b.y*a.z + b.x*a.y*a.z) + + (t3/3.0) *(a.x*a.y*c.z + a.x*b.y*b.z + a.x*c.y*a.z + b.x*a.y*b.z + b.x*b.y*a.z + c.x*a.y*a.z) + + (t4/4.0) *(a.x*b.y*c.z + a.x*c.y*b.z + b.x*a.y*c.z + b.x*b.y*b.z + b.x*c.y*a.z + c.x*a.y*b.z + c.x*b.y*a.z) + + (t5/5.0) *(a.x*c.y*c.z + b.x*b.y*c.z + b.x*c.y*b.z + c.x*a.y*c.z + c.x*b.y*b.z + c.x*c.y*a.z) + + (t6/6.0) *(b.x*c.y*c.z + c.x*b.y*c.z + c.x*c.y*b.z) + + (t7/7.0) *(c.x*c.y*c.z); + + return f; + } + + function sampleFog(pos : Vec3, dir : Vec3, dist : Float) : Float { + return clamp(fogDensity * boxDensity(pos, dir, dist) / fogFade, 0.0, fogDensity); + } + + function integrateBox(pos : Vec3, dir: Vec3, dist : Float, integrationValues : Vec4) : Vec4 { + var extinction = sampleFog(pos, dir, dist/length(dir)); + var clampedExtinction = max(extinction, 1e-5); + var transmittance = exp(-extinction*dist); + var integScatt = fogColor.rgb; + + integrationValues.rgb += integrationValues.a * integScatt; + integrationValues.a *= transmittance; + + return integrationValues; + } + + function evaluate() : Vec4 { + var dir = normalize(transformedPosition - camera.position); + var pos = camera.position; + + dir = dir * global.modelViewInverse.mat3(); + pos = (vec4(pos, 1) * global.modelViewInverse).xyz; + + var path = getPath(pos, dir); + + var integrationValues = vec4(0.0,0.0,0.0,1.0); + return integrateBox(path.xyz, dir, path.w, integrationValues); + } + + function fragment() { + var volumetric = evaluate(); + volumetric.a = saturate(1.0 - volumetric.a); + volumetric.a = volumetric.a > 1.0 - 1e-3 ? 1.0 : volumetric.a; + pixelColor = volumetric; + } + } +} + +class LocalVolumetricLightingObject extends h3d.scene.Object { + + public var localVolume : LocalVolumetricLighting; + public var bounds : h3d.col.OrientedBounds; + + public var mesh : h3d.scene.Mesh; + public var boundsDisplay : h3d.scene.Graphics; + + public var shader : LocalVolumetricShader; + + public function new(parent:h3d.scene.Object, localVolume:LocalVolumetricLighting) { + this.localVolume = localVolume; + super(parent); + bounds = new h3d.col.OrientedBounds(); + + var prim = new h3d.prim.Cube(1,1,1,true); + prim.addNormals(); + mesh = new h3d.scene.Mesh(prim, this); + + var material = mesh.material; + material.castShadows = false; + mesh.material.mainPass.setPassName("volumetricOverlay"); + mesh.material.mainPass.setBlendMode(h3d.mat.BlendMode.Alpha); + + shader = new LocalVolumetricShader(); + + material.mainPass.addShader(shader); + + refresh(); + } + + function isInside(ctx : h3d.scene.RenderContext) : Bool { + var pos = ctx.camera.pos.clone().add(ctx.camera.getForward().scaled(ctx.camera.zNear)); + pos.transform(this.getInvPos()); + return pos.x >= -bounds.hx && pos.x <= bounds.hx && + pos.y >= -bounds.hy && pos.y <= bounds.hy && + pos.z >= -bounds.hz && pos.z <= bounds.hz; + } + + override function sync(ctx : h3d.scene.RenderContext) { + var inside = isInside(ctx); + if(inside){ + mesh.material.mainPass.culling = Front; + mesh.material.mainPass.depthTest = Always; + } else{ + mesh.material.mainPass.culling = Back; + mesh.material.mainPass.depthTest = Less; + } + } + + public function refresh() { + var ob = bounds; + shader.obH.set(ob.hx, ob.hy, ob.hz); + + shader.fogColor.setColor(localVolume.color); + shader.fogDensity = localVolume.fogDensity; + shader.fogFade = localVolume.fogFade; + + if(localVolume.showBounds){ + boundsDisplay = bounds.makeDebugObj(); + boundsDisplay.lineStyle(2, 0xFFFFFF); + addChild(boundsDisplay); + } else if(boundsDisplay != null){ + removeChild(boundsDisplay); + boundsDisplay = null; + } + } +} + +class LocalVolumetricLighting extends hrt.prefab.Object3D { + + var localVolumeObject : LocalVolumetricLightingObject; + + @:s public var fogDensity : Float = 1.0; + @:s public var fogFade : Float = 1.0; + @:s public var color : Int = 0xFFFFFF; + + @:s public var showBounds : Bool = false; + + override function makeObject(parent3d: h3d.scene.Object) : h3d.scene.Object { + localVolumeObject = new LocalVolumetricLightingObject(parent3d, this); + return localVolumeObject; + } + + override function updateInstance(?propName) { + super.updateInstance(propName); + localVolumeObject.refresh(); + } + + #if editor + override function edit( ctx : hide.prefab.EditContext ) { + super.edit(ctx); + ctx.properties.add(new hide.Element(' +
+
+
Density
+
Fade
+
Color
+
+
+
+
+
Show Bounds
+
+
+ '), this, function(pname) { ctx.onChange(this, pname); }); + } + #end + + static var _ = hrt.prefab.Prefab.register("LocalVolumeLighting", LocalVolumetricLighting); +} diff --git a/hrt/prefab/l3d/Text3D.hx b/hrt/prefab/l3d/Text3D.hx index 9def5ce05..065e24544 100644 --- a/hrt/prefab/l3d/Text3D.hx +++ b/hrt/prefab/l3d/Text3D.hx @@ -16,6 +16,7 @@ class Text3DPrimitive extends h2d.TileGroup.TileLayerContent { tmp.push(1); tmp.push(t.u); tmp.push(t.v); + tmp.push(sx + t.width); tmp.push(sy); tmp.push(1); @@ -24,6 +25,7 @@ class Text3DPrimitive extends h2d.TileGroup.TileLayerContent { tmp.push(1); tmp.push(t.u2); tmp.push(t.v); + tmp.push(sx); tmp.push(sy + t.height); tmp.push(1); @@ -32,6 +34,7 @@ class Text3DPrimitive extends h2d.TileGroup.TileLayerContent { tmp.push(1); tmp.push(t.u); tmp.push(t.v2); + tmp.push(sx + t.width); tmp.push(sy + t.height); tmp.push(1); @@ -48,12 +51,29 @@ class Text3DPrimitive extends h2d.TileGroup.TileLayerContent { y += t.height; if( x > xMax ) xMax = x; if( y > yMax ) yMax = y; + + } + } + + override public function alloc(engine:h3d.Engine) { + if( tmp == null ) { + clear(); + indexes = null; + return; + } + if( tmp.length > 0 ) { + buffer = tmp.length < useAllocatorLimit + ? hxd.impl.Allocator.get().ofFloats(tmp, hxd.BufferFormat.POS3D_NORMAL_UV) + : h3d.Buffer.ofFloats(tmp, hxd.BufferFormat.POS3D_NORMAL_UV); + + indexes = engine.mem.getQuadIndexes(buffer.vertices); } } override public function render( engine : h3d.Engine ) { if( tmp == null || tmp.length == 0) return; - super.render(engine); + if( buffer == null || buffer.isDisposed() ) alloc(engine); + engine.renderIndexed(buffer,indexes, 0, buffer.vertices >> 1); } override function getBounds() { @@ -81,6 +101,8 @@ class SignedDistanceField3D extends hxsl.Shader { function fragment() { pixelColor = vec4(color.r, color.g, color.b, smoothstep(alphaCutoff - smoothing, alphaCutoff + smoothing, median(pixelColor.r, pixelColor.g, pixelColor.b))); + if (pixelColor.a <= 0.0) + discard; } } @@ -180,12 +202,11 @@ class Text3D extends Object3D { return; var mesh : h3d.scene.Mesh = cast local3d; var h2dFont = loadFont(); - var h2dText = null/*(cast local2d : h2d.Text)*/; - h2dText.font = h2dFont; - h2dText.letterSpacing = letterSpacing; - h2dText.text = text; - h2dText.smooth = true; - h2dText.textAlign = switch (align) { + text2d.font = h2dFont; + text2d.letterSpacing = letterSpacing; + text2d.text = text; + text2d.smooth = true; + text2d.textAlign = switch (align) { case 1: h2d.Text.Align.Center; case 2: @@ -193,15 +214,17 @@ class Text3D extends Object3D { default: h2d.Text.Align.Left; } - @:privateAccess h2dText.glyphs.content = (cast mesh.primitive : Text3DPrimitive); + @:privateAccess text2d.glyphs.content = (cast mesh.primitive : Text3DPrimitive); @:privateAccess { - h2dText.initGlyphs(text); - h2dText.glyphs.setDefaultColor(color, 1); - mesh.primitive = h2dText.glyphs.content; + text2d.initGlyphs(text); + text2d.glyphs.setDefaultColor(color, 1); + mesh.primitive = text2d.glyphs.content; mesh.material.texture = h2dFont.tile.getTexture(); mesh.material.shadows = false; - mesh.material.mainPass.setPassName("overlay"); - mesh.material.mainPass.depth(false, LessEqual); + mesh.material.mainPass.setPassName("afterTonemapping"); + + //mesh.material.mainPass.setPassName("overlay"); + //mesh.material.mainPass.depth(false, LessEqual); var shader = mesh.material.mainPass.getShader(SignedDistanceField3D); if (shader != null) { diff --git a/hrt/prefab/l3d/modellibrary/Batcher.hx b/hrt/prefab/l3d/modellibrary/Batcher.hx index 08df15626..ed2dc2d18 100644 --- a/hrt/prefab/l3d/modellibrary/Batcher.hx +++ b/hrt/prefab/l3d/modellibrary/Batcher.hx @@ -85,7 +85,7 @@ class Batcher extends h3d.scene.Object { batch.material.mainPass.addShader(library.killAlpha); } - batch.primitiveSubParts = [new h3d.scene.MeshBatch.MeshBatchPart()]; + batch.primitiveSubMeshes = []; } override function onRemove() { diff --git a/hrt/prefab/l3d/modellibrary/GPUBatcher.hx b/hrt/prefab/l3d/modellibrary/GPUBatcher.hx index 60bc5bb71..70e97914f 100644 --- a/hrt/prefab/l3d/modellibrary/GPUBatcher.hx +++ b/hrt/prefab/l3d/modellibrary/GPUBatcher.hx @@ -42,16 +42,18 @@ class GPUBatcher extends Batcher { var batch : h3d.scene.MeshBatch; if(currentBatchCount > GPUMeshBatchThreshold) { var gpuBatch = new h3d.scene.GPUMeshBatch(getPrimitive(), null, this); + gpuBatch.primitiveSubMeshes = []; gpuBatch.enableGpuCulling(); gpuBatch.enableGpuLod(); - gpuBatch.calcBounds = true; batch = gpuBatch; - } else { + } + else { batch = new h3d.scene.MeshBatch(getPrimitive(), null, this); batch.enableStorageBuffer(); } batch.forceGpuUpdate(); + batch.calcBounds = false; setupMeshBatch(batch, props, material); diff --git a/hrt/prefab/l3d/modellibrary/ModelLibrary.hx b/hrt/prefab/l3d/modellibrary/ModelLibrary.hx index e41b81c46..75cc6b279 100644 --- a/hrt/prefab/l3d/modellibrary/ModelLibrary.hx +++ b/hrt/prefab/l3d/modellibrary/ModelLibrary.hx @@ -141,13 +141,28 @@ class ModelLibrary extends Prefab { function emitInstance(bakedMaterial : BakedMaterialData, primitive : h3d.prim.HMDModel, batch : h3d.scene.MeshBatch, ?absPos : h3d.Matrix) { cache.shader.uvTransform.set(bakedMaterial.uvX, bakedMaterial.uvY, bakedMaterial.uvSX, bakedMaterial.uvSY); cache.shader.libraryParams.set(bakedMaterial.texId, 1.0 / atlasResolution / bakedMaterial.uvSX, 0.0, 0.0); - var primitiveSubPart = batch.primitiveSubParts[0]; - primitiveSubPart.indexCount = bakedMaterial.indexCount; - primitiveSubPart.indexStart = bakedMaterial.indexStart; - primitiveSubPart.lodIndexCount = bakedMaterial.lodIndexCount; - primitiveSubPart.lodIndexStart = bakedMaterial.lodIndexStart; - primitiveSubPart.lodConfig = primitive.getLodConfig(); - primitiveSubPart.bounds = cache.geomBounds[bakedMaterial.geomId]; + var curSubMesh = -1; + for ( subMeshIndex => subMesh in batch.primitiveSubMeshes ) { + var subPart = subMesh.subParts[0]; + if ( subPart.indexStart == bakedMaterial.indexStart ) + curSubMesh = subMeshIndex; + } + if ( curSubMesh == -1 ) { + var subPart = new h3d.scene.MeshBatch.SubPart(); + subPart.indexStart = bakedMaterial.indexStart; + subPart.indexCount = bakedMaterial.indexCount; + subPart.lodIndexStart = bakedMaterial.lodIndexStart; + subPart.lodIndexCount = bakedMaterial.lodIndexCount; + subPart.matIndex = 0; + var subMesh = new h3d.scene.MeshBatch.SubMesh(); + subMesh.lodCount = bakedMaterial.lodIndexCount == null ? 1 : bakedMaterial.lodIndexCount.length + 1; + subMesh.lodConfig = primitive.getLodConfig(); + subMesh.subParts = [subPart]; + subMesh.bounds = cache.geomBounds[bakedMaterial.geomId]; + batch.primitiveSubMeshes.push(subMesh); + curSubMesh = batch.primitiveSubMeshes.length - 1; + } + batch.curSubMesh = curSubMesh; if ( absPos != null ) batch.worldPosition = absPos; batch.emitInstance(); diff --git a/hrt/prefab/rfx/ColorGrading.hx b/hrt/prefab/rfx/ColorGrading.hx index 92cad33ed..19b5163a4 100644 --- a/hrt/prefab/rfx/ColorGrading.hx +++ b/hrt/prefab/rfx/ColorGrading.hx @@ -114,7 +114,7 @@ class ColorGrading extends RendererFX { override function modulate(t : Float) { var c : ColorGrading = cast super.modulate(t); c.intensity = this.intensity * t; - return c.instance; + return c; } override function transition( r1 : h3d.impl.RendererFX, r2 : h3d.impl.RendererFX ) : h3d.impl.RendererFX.RFXTransition { diff --git a/hrt/prefab/rfx/RendererFXVolume.hx b/hrt/prefab/rfx/RendererFXVolume.hx deleted file mode 100644 index b5b680a58..000000000 --- a/hrt/prefab/rfx/RendererFXVolume.hx +++ /dev/null @@ -1,238 +0,0 @@ -package hrt.prefab.rfx; - -typedef DebugVolume = { - var color : Int; - var mesh : h3d.scene.Mesh; -} - -class RendererFXVolume extends Object3D { - @:s public var priority : Int; - @:c public var innerShape : h3d.impl.RendererFXVolume.Shape; - @:c public var outerShape : h3d.impl.RendererFXVolume.Shape; - - @:s var debug : Bool = false; - var innerShapeDebug = { color : 0xFFFF00FF, mesh : null }; - var outerShapeDebug = { color : 0xFF00EEFF, mesh : null }; - - public override function new(parent:Prefab, contextShared: ContextShared) { - super(parent, contextShared); - this.innerShape = Sphere(1); - this.outerShape = Sphere(1); - } - - override function load(data: Dynamic) { - super.load(data); - - function loadShape(shape : Dynamic) : h3d.impl.RendererFXVolume.Shape { - if (shape == null) - return Sphere(1); - - return switch (shape.shape) { - case 0: - return Sphere(shape.radius); - case 1: - return Box(shape.width, shape.height); - default: - throw "not implemented"; - }; - } - - this.innerShape = loadShape(data.innerShape); - this.outerShape = loadShape(data.outerShape); - } - - override function copy(data: Dynamic) : Void { - super.copy(data); - - var s : RendererFXVolume = cast data; - this.load(s.save()); - } - - override function save() : Dynamic { - var obj = super.save(); - - function saveShape(shape : h3d.impl.RendererFXVolume.Shape) : Dynamic { - if (shape == null) - return { shape: 0, radius: 1 }; - - return switch (shape) { - case Sphere(radius): - { shape: 0, radius: radius }; - case Box(width, height): - { shape: 1, width: width, height: height }; - default: - throw { shape: 0, radius: 1 }; - }; - } - - obj.innerShape = saveShape(this.innerShape); - obj.outerShape = saveShape(this.outerShape); - return obj; - } - - override function makeObject(parent3d: h3d.scene.Object) { - var o = new h3d.impl.RendererFXVolume(parent3d); - o.innerShape = this.innerShape; - o.outerShape = this.outerShape; - o.priority = this.priority; - - return o; - } - - override function postMakeInstance() { - var o : h3d.impl.RendererFXVolume = cast local3d; - var rendererFxs : Array = cast findAll((p) -> { return Std.isOfType(p, RendererFX) && p.enabled; }); - o.effects = []; - for (r in rendererFxs) - o.effects.push(@:privateAccess r.instance); - } - - override function updateInstance(?propName : String) { - super.updateInstance(propName); - - var volume : h3d.impl.RendererFXVolume = cast local3d; - volume.innerShape = this.innerShape; - volume.outerShape = this.outerShape; - volume.priority = this.priority; - - function applyDebug(sprDebug : DebugVolume, shape : h3d.impl.RendererFXVolume.Shape) { - if (sprDebug != null) { - sprDebug.mesh.remove(); - sprDebug.mesh = null; - } - - if (!debug) return; - - sprDebug.mesh = new h3d.scene.Mesh(createPrim(shape), local3d); - sprDebug.mesh.name = "SpatialRendererFXDebug"; - sprDebug.mesh.material.mainPass.depth(true, LessEqual); - var s = new h3d.shader.AlphaMult(); - s.alpha = 0.3; - sprDebug.mesh.material.mainPass.addShader(s); - sprDebug.mesh.material.blendMode = Alpha; - sprDebug.mesh.material.mainPass.setPassName("overlay"); - sprDebug.mesh.ignoreParentTransform = false; - var c = hrt.impl.ColorSpace.Color.fromInt(sprDebug.color); - hrt.impl.ColorSpace.iRGBtofRGB(c, sprDebug.mesh.material.color); - } - - applyDebug(innerShapeDebug, this.innerShape); - applyDebug(outerShapeDebug, this.outerShape); - } - - #if editor - override function getHideProps() : hide.prefab.HideProps { - return { name : Type.getClassName(Type.getClass(this)).split(".").pop(), icon : "cubes" }; - } - - override function edit(ctx:hide.prefab.EditContext) { - var e = new hide.Element(' -
-
-
Debug
-
Priority
-
Shape
-
-
-
-
- '); - - var shapeSel = e.find("#shape-sel"); - for (idx => el in Type.getEnumConstructs(h3d.impl.RendererFXVolume.Shape)) - shapeSel.append(new hide.Element('')); - shapeSel.on("change", function(e) { - var prevInner = this.innerShape; - var prevOuter = this.outerShape; - - switch (shapeSel.val()) { - case "Sphere": - this.innerShape = Sphere(10); - this.outerShape = Sphere(15); - case "Box": - this.innerShape = Box(10, 10); - this.outerShape = Box(15, 15); - default: - } - - var newInner = this.innerShape; - var newOuter = this.outerShape; - - ctx.properties.undo.change(Custom(function(undo) { - this.innerShape = undo ? prevInner : newInner; - this.outerShape = undo ? prevOuter : newOuter; - ctx.rebuildProperties(); - })); - - ctx.rebuildProperties(); - }); - - var paramsEl = e.find("#params"); - var param : hide.Element = null; - function onChange() { - var prevInner = this.innerShape; - var prevOuter = this.outerShape; - - switch (this.innerShape) { - case Sphere(_): - this.innerShape = Sphere(Std.parseFloat(param.find('#innerRadius').val())); - this.outerShape = Sphere(Std.parseFloat(param.find('#outerRadius').val())); - case Box(_): - } - - var newInner = this.innerShape; - var newOuter = this.outerShape; - - ctx.properties.undo.change(Custom(function(undo) { - this.innerShape = undo ? prevInner : newInner; - this.outerShape = undo ? prevOuter : newOuter; - ctx.rebuildProperties(); - this.updateInstance(); - })); - - this.updateInstance(); - } - switch ([this.innerShape, this.outerShape]) { - case [Sphere(r1), Sphere(r2)]: - param = new hide.Element('
-
Inner radius
-
Outer Radius
-
'); - - function setup(e : hide.Element, value : Dynamic) { - e.val(value); - e.on("change", () -> onChange()); - } - setup(param.find('#innerRadius'), r1); - setup(param.find('#outerRadius'), r2); - - default: - param = new hide.Element('

Not supported

'); - } - paramsEl.append(param); - ctx.properties.add(e, this); - } - #end - - function createPrim(shape : h3d.impl.RendererFXVolume.Shape) : h3d.prim.Primitive { - switch(shape) { - case Sphere(radius): - var prim = new h3d.prim.Sphere(radius, 64, 64); - prim.addNormals(); - prim.addUVs(); - return prim; - case Box(width, height): - var prim = new h3d.prim.Cube(width); - prim.addNormals(); - prim.addUVs(); - return prim; - } - } - - public function getFactor(pos : h3d.col.Point) : Float { - if (local3d == null) return 0.; - return cast(local3d, h3d.impl.RendererFXVolume).getFactor(pos); - } - - static var _ = Prefab.register("RendererFXVolume", RendererFXVolume); -} \ No newline at end of file diff --git a/hrt/prefab/rfx/Sharpen.hx b/hrt/prefab/rfx/Sharpen.hx index ae40af596..e052c1cff 100644 --- a/hrt/prefab/rfx/Sharpen.hx +++ b/hrt/prefab/rfx/Sharpen.hx @@ -34,7 +34,7 @@ class Sharpen extends RendererFX { r.mark("Sharpen"); var sharpen = r.allocTarget("sharpen", true, 1.0, RGBA); var ctx = r.ctx; - ctx.engine.pushTarget(sharpen); + ctx.engine.pushTarget(sharpen, NotBound); sharpenPass.shader.source = ctx.getGlobal("ldrMap"); sharpenPass.shader.intensity = intensity; sharpenPass.shader.resolution.set(ctx.engine.width, ctx.engine.height); @@ -53,7 +53,9 @@ class Sharpen extends RendererFX {
Intensity
- '),this); + '), this, function(pname) { + ctx.onChange(this,pname); + }); } #end diff --git a/hrt/prefab/rfx/VolumetricLighting.hx b/hrt/prefab/rfx/VolumetricLighting.hx index 4bda5d949..1b8b7818b 100644 --- a/hrt/prefab/rfx/VolumetricLighting.hx +++ b/hrt/prefab/rfx/VolumetricLighting.hx @@ -130,7 +130,7 @@ class VolumetricLightingShader extends h3d.shader.pbr.DefaultForward { var skipShadow : Bool = false; function evaluateCascadeShadow() : Float { - var i = dirLightStride + pointLightStride + spotLightStride; + var i = cascadeLightStride; var shadow = 1.0; var shadowProj = mat3x4(lightInfos[i + 2], lightInfos[i + 3], lightInfos[i + 4]); diff --git a/hrt/shader/Terrain.hx b/hrt/shader/Terrain.hx index 6d48f54d0..2f52f909b 100644 --- a/hrt/shader/Terrain.hx +++ b/hrt/shader/Terrain.hx @@ -111,7 +111,7 @@ class Terrain extends hxsl.Shader { var viewNS = normalize(camera.position - transformedPosition) * TBN; viewNS.xy /= viewNS.z; viewNS.x *= -1; - var numLayers = mix(float(maxStep), float(minStep), viewNS.dot(terrainNormal)); + var numLayers = mix(float(maxStep), float(minStep), saturate(viewNS.dot(terrainNormal))); var layerDepth = 1.0 / numLayers; var curLayerDepth = 0.; var delta = (viewNS.xy * parallaxAmount / primSize) / numLayers; diff --git a/hrt/shgraph/Macros.hx b/hrt/shgraph/Macros.hx index 1a8a47d2b..666bbe903 100644 --- a/hrt/shgraph/Macros.hx +++ b/hrt/shgraph/Macros.hx @@ -107,8 +107,7 @@ class Macros { } expr.iter(iter); - var shader = new hxsl.MacroParser().parseExpr(expr); - f.kind = FVar(null, macro @:pos(pos) $v{shader}); + var shaderExpr = new hxsl.MacroParser().parseExpr(expr); var name = Std.string(c); var check = new hxsl.Checker(); @@ -118,30 +117,15 @@ class Macros { check.loadShader = loadShader; - var shader = check.check(name, shader); - //trace(shader); - //Printer.check(shader); + var shader = check.check(name, shaderExpr); var serializer = new hxsl.Serializer(); - var str = Context.defined("display") ? "" : serializer.serialize(shader); + var str = serializer.serialize(shader); f.kind = FVar(null, { expr : EConst(CString(str)), pos : pos } ); f.meta.push({ name : ":keep", pos : pos, }); - function makeField(name: String, arr: Array) : Field - { - return { - name: name, - access: [APublic, AStatic], - kind: FVar(macro : Array, macro $v{arr}), - pos: f.pos, - meta: [{ - name : ":keep", - pos : pos,} - ], - }; - } var finalMap : Map = []; diff --git a/hrt/shgraph/ShaderConst.hx b/hrt/shgraph/ShaderConst.hx index d767edb77..8bf378c92 100644 --- a/hrt/shgraph/ShaderConst.hx +++ b/hrt/shgraph/ShaderConst.hx @@ -14,10 +14,16 @@ class ShaderConst extends ShaderNode { override public function getPropertiesHTML(width : Float) : Array { var elements = super.getPropertiesHTML(width); - var element = new hide.Element('
'); - element.append(new hide.Element('')); + var element = new hide.Element('
'); + + var editBtn = new hide.Element('
'); + + element.append(editBtn); + + var input = new hide.Element(''); + + element.append(input); - var input = element.children("input"); input.on("keydown", function(e) { e.stopPropagation(); }); @@ -25,6 +31,11 @@ class ShaderConst extends ShaderNode { this.name = input.val(); }); + editBtn.on("click", function(e) { + input.focus(); + input.select(); + }); + elements.push(element); return elements; diff --git a/hrt/shgraph/ShaderNodeHxsl.hx b/hrt/shgraph/ShaderNodeHxsl.hx index a177bcaaf..fbe577687 100644 --- a/hrt/shgraph/ShaderNodeHxsl.hx +++ b/hrt/shgraph/ShaderNodeHxsl.hx @@ -7,7 +7,18 @@ import hrt.tools.MapUtils; using Lambda; -typedef CacheEntry = {expr: TExpr, funs: Array, inputs: Array, outputs: Array, idInputOrder: Map, idOutputOrder: Map}; +typedef FunctionCache = { + fun: TFunction, + useSgIO: Bool, +} +typedef CacheEntry = { + expr: TExpr, + funs: Array, + inputs: Array, + outputs: Array, + idInputOrder: Map, + idOutputOrder: Map, +}; class CustomSerializer extends hxsl.Serializer { @@ -83,18 +94,6 @@ class ShaderNodeHxsl extends ShaderNode { var unser = new CustomSerializer(); var data = @:privateAccess unser.unserialize(toUnser); - var funs = []; - var expr : TExpr = null; - for (fn in data.funs) { - if (fn.ref.name == "fragment") { - expr = fn.expr; - break; - } else { - fn.ref.name = shortName + "_" + fn.ref.name; // De-duplicate function name if multiple nodes declare the same function name to avoid conflics - funs.push(fn); - } - } - var inputs : Array = []; var outputs : Array = []; var idInputOrder : Map = []; @@ -117,6 +116,42 @@ class ShaderNodeHxsl extends ShaderNode { } } + var funs : Array = []; + var expr : TExpr = null; + for (fn in data.funs) { + if (fn.ref.name == "fragment") { + expr = fn.expr; + break; + } else { + fn.ref.name = shortName + "_" + fn.ref.name; // De-duplicate function name if multiple nodes declare the same function name to avoid conflics + + var useSgIO = false; + function hasShaderInput(e: TExpr) : Void { + switch (e.e) { + case TVar(v): + switch(infos.get(v.id)) { + case SgInput(isDynamic, defaultValue): + useSgIO = true; + return; + case SgOutput(_): + useSgIO = true; + case null: + default: + } + default: + if (!useSgIO) + e.iter(hasShaderInput); + } + }; + fn.expr.iter(hasShaderInput); + + funs.push({ + fun: fn, + useSgIO: useSgIO, + }); + } + } + return {expr: expr, funs: funs, inputs: inputs, outputs: outputs, idInputOrder: idInputOrder, idOutputOrder: idOutputOrder}; } @@ -225,7 +260,58 @@ class ShaderNodeHxsl extends ShaderNode { } } + var funs: Array = []; + + for (fun in cache.funs) { + + if (fun.useSgIO) { + // If the function use input/outputs, we need to duplicate it per Node invocation, + // because we need to patch the function to properly set the input/outputs + var fun = fun.fun; + var tvar = MapUtils.getOrPut(varsRemap, fun.ref.id, + { + name: '${fun.ref.name}_$id', + id: hxsl.Ast.Tools.allocVarId(), + type: fun.ref.type, + kind: fun.ref.kind, + parent: fun.ref.parent, + qualifiers: fun.ref.qualifiers, + }); + + var args : Array = []; + for (arg in fun.args) { + var tvar = MapUtils.getOrPut(varsRemap, arg.id, + { + name: arg.name, + id: hxsl.Ast.Tools.allocVarId(), + type: arg.type, + kind: arg.kind, + parent: arg.parent, + qualifiers: arg.qualifiers, + }); + args.push(tvar); + } + + var replacementFunc : TFunction = { + ref: tvar, + expr: fun.expr, + ret: fun.ret, + args: args, + kind: fun.kind + } + + funs.push({fun: replacementFunc, useSgIO: true}); + } + else { + funs.push(fun); + } + } + var expr = patch(cache.expr); + for (fun in funs) { + if (fun.useSgIO) + fun.fun.expr = patch(fun.fun.expr); + } if (genFailure) { for (outputId => o in cache.outputs) { @@ -258,8 +344,8 @@ class ShaderNodeHxsl extends ShaderNode { } } - for (func in cache.funs) { - ctx.addFunction(func); + for (func in funs) { + ctx.addFunction(func.fun); } } diff --git a/hrt/shgraph/nodes/BoolConst.hx b/hrt/shgraph/nodes/BoolConst.hx index 48fda3a48..e13e93bf3 100644 --- a/hrt/shgraph/nodes/BoolConst.hx +++ b/hrt/shgraph/nodes/BoolConst.hx @@ -2,11 +2,10 @@ package hrt.shgraph.nodes; using hxsl.Ast; -@name("Bool") +@name("Const Bool") @description("Boolean input (static)") @group("Property") @width(100) -@noheader() class BoolConst extends ShaderConst { @@ -26,7 +25,7 @@ class BoolConst extends ShaderConst { #if editor override public function getPropertiesHTML(width : Float) : Array { var elements = super.getPropertiesHTML(width); - var element = new hide.Element('
'); + var element = new hide.Element('
'); element.append(new hide.Element('')); var input = element.children("input"); diff --git a/hrt/shgraph/nodes/Color.hx b/hrt/shgraph/nodes/Color.hx index 5658337bf..60808cd19 100644 --- a/hrt/shgraph/nodes/Color.hx +++ b/hrt/shgraph/nodes/Color.hx @@ -2,11 +2,10 @@ package hrt.shgraph.nodes; using hxsl.Ast; -@name("Color") +@name("Const Color") @description("Color property (static)") @group("Property") @width(100) -@noheader() class Color extends ShaderConst { @prop() var r : Float = 0; @@ -28,9 +27,9 @@ class Color extends ShaderConst { #if editor override public function getPropertiesHTML(width : Float) : Array { var elements = super.getPropertiesHTML(width); - var element = new hide.Element('
'); + var element = new hide.Element('
'); var picker = new hide.comp.ColorPicker.ColorBox(element, true, true); - + element.height(picker.element.height()); var start = h3d.Vector4.fromArray([r, g, b, a]); picker.value = start.toColor(); diff --git a/hrt/shgraph/nodes/FloatConst.hx b/hrt/shgraph/nodes/FloatConst.hx index 850a98964..5cd7b7c0c 100644 --- a/hrt/shgraph/nodes/FloatConst.hx +++ b/hrt/shgraph/nodes/FloatConst.hx @@ -2,11 +2,11 @@ package hrt.shgraph.nodes; using hxsl.Ast; -@name("Value") +@name("Const Value") +@alias("Const Float") @description("Number input (static)") @group("Property") @width(100) -@noheader() class FloatConst extends ShaderConst { override function getOutputs() { @@ -25,8 +25,8 @@ class FloatConst extends ShaderConst { #if editor override public function getPropertiesHTML(width : Float) : Array { var elements = super.getPropertiesHTML(width); - var element = new hide.Element('
'); - element.append(new hide.Element('')); + var element = new hide.Element('
'); + element.append(new hide.Element('')); var input = element.children("input"); input.on("keydown", function(e) {