diff --git a/bin/app.html b/bin/app.html index 07dbe3bce..82026bf25 100644 --- a/bin/app.html +++ b/bin/app.html @@ -24,10 +24,29 @@ @@ -153,6 +176,10 @@ var ext = file.split(".").pop().toLowerCase(); if( ext == "js") { + // destroy self if we are in thumbnail generation mode + if (thumbnail) + nw.Window.get().close(true); + if (timer != 0) clearTimeout(timer); timer = setTimeout(function() { diff --git a/bin/res/icons/svg/LICENCE.txt b/bin/res/icons/svg/LICENCE.txt new file mode 100644 index 000000000..3a5db03f5 --- /dev/null +++ b/bin/res/icons/svg/LICENCE.txt @@ -0,0 +1,429 @@ +Icons from Blender https://ui.blender.org/icons + +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. \ No newline at end of file diff --git a/bin/res/icons/svg/big_folder.svg b/bin/res/icons/svg/big_folder.svg new file mode 100644 index 000000000..7cfd500d2 --- /dev/null +++ b/bin/res/icons/svg/big_folder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/res/icons/svg/close.svg b/bin/res/icons/svg/close.svg new file mode 100644 index 000000000..966cb8880 --- /dev/null +++ b/bin/res/icons/svg/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/res/icons/svg/file.svg b/bin/res/icons/svg/file.svg new file mode 100644 index 000000000..fb8960e00 --- /dev/null +++ b/bin/res/icons/svg/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/res/icons/svg/file_parent.svg b/bin/res/icons/svg/file_parent.svg new file mode 100644 index 000000000..e1353e13e --- /dev/null +++ b/bin/res/icons/svg/file_parent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/res/icons/svg/generate.hx b/bin/res/icons/svg/generate.hx new file mode 100644 index 000000000..139597f9c --- /dev/null +++ b/bin/res/icons/svg/generate.hx @@ -0,0 +1,2 @@ + + diff --git a/bin/res/icons/svg/icons.css b/bin/res/icons/svg/icons.css new file mode 100644 index 000000000..e69de29bb diff --git a/bin/res/icons/svg/icons.less b/bin/res/icons/svg/icons.less new file mode 100644 index 000000000..e69de29bb diff --git a/bin/res/icons/svg/loading.svg b/bin/res/icons/svg/loading.svg new file mode 100644 index 000000000..1fb32604d --- /dev/null +++ b/bin/res/icons/svg/loading.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/bin/res/icons/svg/right_caret.svg b/bin/res/icons/svg/right_caret.svg new file mode 100644 index 000000000..d788d5005 --- /dev/null +++ b/bin/res/icons/svg/right_caret.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/res/icons/svg/search.svg b/bin/res/icons/svg/search.svg new file mode 100644 index 000000000..78fd49a5f --- /dev/null +++ b/bin/res/icons/svg/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/res/icons/svg/unknown_file.svg b/bin/res/icons/svg/unknown_file.svg new file mode 100644 index 000000000..ecdc2098b --- /dev/null +++ b/bin/res/icons/svg/unknown_file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/style.css b/bin/style.css index 26940fe0f..ac5be1a7c 100644 --- a/bin/style.css +++ b/bin/style.css @@ -4312,11 +4312,13 @@ hide-popover hide-content { } :root { --hover-highlight: rgba(114, 180, 255, 0.5); - --selection: rgba(114, 180, 255); + --selection: #3185ce; --hover: #444; --basic-shadow: 2px 2px 3px rgba(0, 0, 0, 0.75); --sublte-shadow: 1px 1px 3px rgba(0, 0, 0, 0.33); --basic-border: 1px solid #353535; + --basic-border-hover: 1px solid #555; + --basic-border-focused: 1px solid var(--selection); --basic-border-radius: 5px; --basic-padding: 0.2em; --bg-0: #000; @@ -4471,6 +4473,14 @@ fancy-toolbar { display: flex; flex-direction: row; align-items: center; + padding-left: 0.25em; + padding-right: 0.25em; +} +fancy-toolbar.shadow { + position: relative; + background-color: #272727; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2); + z-index: 999; } fancy-button { position: relative; @@ -4918,3 +4928,488 @@ blend-space-2d-root properties-container .hide-properties dl > div .hide-range i .fancy-hide { display: none; } +file-browser { + display: flex; + flex-direction: row; + height: 100%; +} +file-browser .left { + width: 300px; + min-width: 100px; + background-color: mediumaquamarine; +} +file-browser .right { + flex: 1 1; + display: flex; + flex-direction: column; +} +fancy-flex-fill { + flex: 1 1; +} +fancy-icon { + height: 1em; + display: block; + mask-size: contain; + background-color: currentColor; + mask-mode: luminance; + mask-repeat: no-repeat; + aspect-ratio: 1/1; + mask-position: center; +} +fancy-icon.small { + height: 12px; +} +fancy-icon.big { + height: 22px; +} +fancy-icon.fi-right-caret { + mask-image: url("res/icons/svg/right_caret.svg"); +} +fancy-icon.fi-close { + mask-image: url("res/icons/svg/close.svg"); +} +fancy-icon.fi-search { + mask-image: url("res/icons/svg/search.svg"); +} +fancy-closable { + display: flex; + align-items: center; + justify-content: center; + background-color: #333; + box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.33); + margin-bottom: 2px; + overflow: hidden; + height: 0px; +} +fancy-closable * { + box-sizing: border-box; +} +fancy-search { + position: relative; + flex: 1 1; + padding: 2px; + max-width: 400px; +} +fancy-search, +fancy-search * { + box-sizing: border-box; +} +fancy-search > input { + border: var(--basic-border); + border-radius: var(--basic-border-radius); + min-width: 0; + width: 100%; + height: 100%; +} +fancy-search > input:hover { + border: var(--basic-border-hover); +} +fancy-search > input:focus { + border: var(--basic-border-focused); + outline: none; +} +fancy-search .search-icon { + position: absolute; + right: 0.5em; + top: 50%; + transform: translateY(-50%); + color: #999; + z-index: 1; +} +fancy-tree { + --anim-speed: 0.1s; + background-color: #222; + height: 100%; + display: flex; + flex-direction: column; +} +fancy-tree:focus { + outline: none; +} +fancy-tree fancy-wrapper { + display: block; + width: 100%; + flex: 1 1; + min-width: 100px; + overflow-x: clip; + overflow-y: auto; +} +fancy-tree fancy-wrapper fancy-tree-item { + --depth: 0; + display: flex; + flex-direction: column; + --highlight: 0; + box-sizing: border-box; +} +fancy-tree fancy-wrapper fancy-tree-item.hide-search { + overflow: hidden; + height: 0px; + display: none; +} +fancy-tree fancy-wrapper fancy-tree-item fancy-tree-children { + height: 0px; + overflow: hidden; + display: none; +} +fancy-tree fancy-wrapper fancy-tree-item.open > fancy-tree-children { + display: block; + height: auto; +} +fancy-tree fancy-wrapper fancy-tree-item.open > fancy-tree-children:has(fancy-tree-name:focus) { + overflow: visible; +} +fancy-tree fancy-wrapper fancy-tree-item.open > fancy-tree-header .caret::before { + transform: rotate(90deg); +} +fancy-tree fancy-wrapper fancy-tree-item:has(fancy-tree-children .selected) > fancy-tree-header fancy-tree-name { + text-decoration: underline var(--selection); +} +fancy-tree fancy-wrapper fancy-tree-item.current:not(.selected) { + --highlight: 10; +} +fancy-tree fancy-wrapper fancy-tree-item.selected > fancy-tree-header { + color: white; + --background: var(--selection); +} +fancy-tree fancy-wrapper fancy-tree-item fancy-tree-header { + padding-left: calc((var(--depth) - 1) * 1em + 0.25em); + box-sizing: border-box; + height: 20px; + max-height: 20px; + min-height: 20px; + --background: #222; + background-color: hsl(from var(--background) h s calc(var(--highlight) + l)); + display: flex; + position: relative; +} +fancy-tree fancy-wrapper fancy-tree-item fancy-tree-header:has(>.caret:hover, >fancy-tree-name:hover) { + --highlight: 10; +} +fancy-tree fancy-wrapper fancy-tree-item fancy-tree-header fancy-tree-icon { + align-self: center; + width: 16px; + height: 16px; + display: block; + flex-shrink: 0; + filter: drop-shadow(1px 1px rgba(0, 0, 0, 0.5)); +} +fancy-tree fancy-wrapper fancy-tree-item fancy-tree-header fancy-tree-icon.caret { + width: 16px; + height: 16px; +} +fancy-tree fancy-wrapper fancy-tree-item fancy-tree-header fancy-tree-icon.caret::before { + content: ""; + position: absolute; + top: 0; + left: 0; + margin: 2px; + width: 12px; + height: 12px; + transition: transform var(--anim-speed); + background-color: currentColor; + mask-image: url("res/icons/svg/right_caret.svg"); + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + pointer-events: all; + cursor: pointer; +} +fancy-tree fancy-wrapper fancy-tree-item fancy-tree-header fancy-tree-name { + display: block; + text-overflow: ellipsis; + align-self: stretch; + overflow: clip; + text-wrap: nowrap; + flex-shrink: 1; + cursor: pointer; + filter: drop-shadow(1px 1px rgba(0, 0, 0, 0.5)); + min-width: 2em; + padding: 1px; + padding-left: 0; + align-content: center; +} +fancy-tree fancy-wrapper fancy-tree-item fancy-tree-header fancy-tree-name:focus { + z-index: 100; + background-color: #222; + outline-offset: 1px; +} +fancy-tree fancy-wrapper fancy-tree-item fancy-tree-header.feedback-drop-top::after { + position: absolute; + content: ""; + display: block; + left: 0; + right: 0; + top: 0; + border-top: solid 1px var(--selection); + z-index: 10; + pointer-events: none; +} +fancy-tree fancy-wrapper fancy-tree-item fancy-tree-header.feedback-drop-bot::after { + position: absolute; + content: ""; + display: block; + left: 0; + right: 0; + bottom: 0; + border-top: solid 1px var(--selection); + z-index: 10; + pointer-events: none; +} +fancy-tree fancy-wrapper fancy-tree-item fancy-tree-header.feedback-drop-in::after { + position: absolute; + content: ""; + display: block; + left: 0; + right: 0; + bottom: 0; + top: 0; + border: solid 1px var(--selection); + z-index: 10; + pointer-events: none; +} +fancy-tree2 { + --anim-speed: 0.1s; + background-color: #222; + height: 100%; + display: flex; + flex-direction: column; +} +fancy-tree2:focus { + outline: none; +} +fancy-tree2 fancy-scroll { + display: block; + width: 100%; + flex: 1 1; + min-width: 100px; + overflow-x: clip; + overflow-y: auto; +} +fancy-tree2 fancy-scroll fancy-item-container { + position: relative; + display: block; + overflow: hidden; + contain: layout paint size style; +} +fancy-tree2 fancy-scroll fancy-tree-item { + position: absolute; + --depth: 0; + --highlight: 0; + width: 100%; + padding-left: calc((var(--depth) - 1) * 1em + 0.25em); + box-sizing: border-box; + height: 20px; + max-height: 20px; + min-height: 20px; + --background: #222; + background-color: hsl(from var(--background) h s calc(var(--highlight) + l)); + display: flex; +} +fancy-tree2 fancy-scroll fancy-tree-item.hide-search { + overflow: hidden; + height: 0px; + display: none; +} +fancy-tree2 fancy-scroll fancy-tree-item.open > .caret::before { + transform: rotate(90deg); +} +fancy-tree2 fancy-scroll fancy-tree-item:has(fancy-tree-children .selected) > fancy-tree-header fancy-tree-name { + text-decoration: underline var(--selection); +} +fancy-tree2 fancy-scroll fancy-tree-item.current { + outline: dashed 1px #AAA; + z-index: 10; + outline-offset: -1px; +} +fancy-tree2 fancy-scroll fancy-tree-item.selected { + color: white; + --background: var(--selection); +} +fancy-tree2 fancy-scroll fancy-tree-item:has(>:hover) { + --highlight: 10; + cursor: pointer; +} +fancy-tree2 fancy-scroll fancy-tree-item .hidden { + visibility: hidden; + pointer-events: none; +} +fancy-tree2 fancy-scroll fancy-tree-item fancy-tree-icon { + align-self: center; + width: 16px; + height: 16px; + display: block; + flex-shrink: 0; + filter: drop-shadow(1px 1px rgba(0, 0, 0, 0.5)); +} +fancy-tree2 fancy-scroll fancy-tree-item fancy-tree-icon.caret { + width: 16px; + height: 16px; +} +fancy-tree2 fancy-scroll fancy-tree-item fancy-tree-icon.caret::before { + content: ""; + position: absolute; + top: 0; + left: 0; + margin: 2px; + width: 12px; + height: 12px; + transition: transform var(--anim-speed); + background-color: currentColor; + mask-image: url("res/icons/svg/right_caret.svg"); + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + pointer-events: all; +} +fancy-tree2 fancy-scroll fancy-tree-item fancy-tree-name { + display: block; + text-overflow: ellipsis; + align-self: stretch; + overflow: clip; + text-wrap: nowrap; + flex-shrink: 1; + filter: drop-shadow(1px 1px rgba(0, 0, 0, 0.5)); + min-width: 2em; + padding: 1px; + padding-left: 0; + align-content: center; +} +fancy-tree2 fancy-scroll fancy-tree-item fancy-tree-name:focus { + z-index: 100; + background-color: #222; + outline-offset: 1px; +} +fancy-tree2 fancy-scroll fancy-tree-item fancy-tree-name > span.search-hl { + background-color: var(--selection); + color: white; +} +fancy-tree2 fancy-scroll fancy-tree-item.feedback-drop-top::after { + position: absolute; + content: ""; + display: block; + left: 0; + right: 0; + top: 0; + border-top: solid 1px var(--selection); + z-index: 10; + pointer-events: none; +} +fancy-tree2 fancy-scroll fancy-tree-item.feedback-drop-bot::after { + position: absolute; + content: ""; + display: block; + left: 0; + right: 0; + bottom: 0; + border-top: solid 1px var(--selection); + z-index: 10; + pointer-events: none; +} +fancy-tree2 fancy-scroll fancy-tree-item.feedback-drop-in::after { + position: absolute; + content: ""; + display: block; + left: 0; + right: 0; + bottom: 0; + top: 0; + border: solid 1px var(--selection); + z-index: 10; + pointer-events: none; +} +fancy-gallery { + display: flex; + flex-direction: column; + overflow: none; + flex-grow: 0; + flex-shrink: 1; + align-self: stretch; + min-width: 0; + min-height: 0; + width: 100%; +} +fancy-gallery, +fancy-gallery * { + box-sizing: border-box; +} +fancy-gallery fancy-scroll { + flex: 1 1; + overflow-y: scroll; +} +fancy-gallery fancy-scroll fancy-item-container { + position: relative; + display: block; + overflow: hidden; + width: 100%; + min-height: 100%; + contain: layout paint size style; +} +fancy-gallery fancy-scroll fancy-item-container fancy-item { + padding: 4px; + position: absolute; + align-items: stretch; + overflow: hidden; + contain: layout paint size style; + background-color: #282828; + border-radius: 6px; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.4); +} +fancy-gallery fancy-scroll fancy-item-container fancy-item:hover { + background-color: var(--hover); +} +fancy-gallery fancy-scroll fancy-item-container fancy-item.details { + padding: 0; + display: flex; + align-items: center; + box-shadow: unset; + border-radius: unset; +} +fancy-gallery fancy-scroll fancy-item-container fancy-item fancy-thumbnail { + flex: 0 1; + display: block; + width: 100%; + height: 100%; +} +fancy-gallery fancy-scroll fancy-item-container fancy-item .thumb { + transform: unset; +} +fancy-gallery fancy-scroll fancy-item-container fancy-item .loading { + animation: spinner 5s linear infinite; + transform: rotate(0deg); +} +@keyframes spinner { + to { + transform: rotate(360deg); + } +} +fancy-gallery fancy-scroll fancy-item-container fancy-item.details fancy-image { + height: 100%; + width: auto; + aspect-ratio: 1 / 1; +} +fancy-gallery fancy-scroll fancy-item-container fancy-item fancy-name { + position: absolute; + bottom: 0px; + left: 0px; + right: 0px; + height: 32px; + display: block; + text-align: center; + text-overflow: ellipsis; + overflow: hidden; +} +fancy-gallery fancy-scroll fancy-item-container fancy-item.details fancy-name { + position: relative; + height: auto; + flex: 1 1; + text-align: left; +} +fancy-image { + display: block; + width: 100%; + aspect-ratio: 1 / 1; + background-position: center; + background-repeat: no-repeat; + background-size: cover; + transform: scale(0.75, 0.75); +} diff --git a/bin/style.less b/bin/style.less index 1b6b3836e..5b442e226 100644 --- a/bin/style.less +++ b/bin/style.less @@ -5110,13 +5110,16 @@ hide-popover { :root { --hover-highlight: rgba(114, 180, 255, 0.5); - --selection: rgba(114, 180, 255); + --selection: #3185ce; --hover: #444; --basic-shadow: 2px 2px 3px rgba(0,0,0,.75); --sublte-shadow: 1px 1px 3px rgba(0,0,0,.33); --basic-border: 1px solid #353535; + --basic-border-hover: 1px solid #555; + --basic-border-focused: 1px solid var(--selection); + --basic-border-radius: 5px; --basic-padding: 0.2em; @@ -5316,6 +5319,16 @@ fancy-toolbar { display: flex; flex-direction: row; align-items: center; + + &.shadow { + position: relative; + background-color: #272727; + box-shadow: 2px 2px 2px rgba(0,0,0,0.2); + z-index: 999; + } + + padding-left: 0.25em; + padding-right: 0.25em; } fancy-button { @@ -5383,7 +5396,6 @@ fancy-button { &:hover { background: none; color: var(--fancy-main-text-color); - } } @@ -5879,4 +5891,623 @@ blend-space-2d-root { .fancy-hide { display: none; +} + +file-browser { + display: flex; + flex-direction: row; + height: 100%; + + .left { + width: 300px; + min-width: 100px; + background-color: mediumaquamarine; + } + + .right { + flex: 1 1; + display: flex; + flex-direction: column; + } +} + +fancy-flex-fill { + flex: 1 1; +} + +fancy-icon { + aspect-ratio: 1/1; + height: 1em; + display: block; + mask-size: contain; + background-color: currentColor; + mask-mode: luminance; + mask-repeat: no-repeat; + aspect-ratio: 1/1; + mask-position: center; + + &.small { + height: 12px; + } + + + &.big { + height: 22px; + } + + &.fi-right-caret { mask-image: url("res/icons/svg/right_caret.svg"); } + &.fi-close { mask-image: url("res/icons/svg/close.svg"); } + &.fi-search { mask-image: url("res/icons/svg/search.svg"); } +} + +fancy-closable { + display: flex; + align-items: center; + justify-content: center; + + * { + box-sizing: border-box; + } + + background-color: #333; + box-shadow: 1px 1px 1px rgba(0,0,0,0.33); + margin-bottom: 2px; + + overflow: hidden; + height: 0px; +} + +fancy-search { + position: relative; + flex: 1 1; + padding: 2px; + max-width: 400px; + + &, * { + box-sizing: border-box; + } + + > input { + border: var(--basic-border); + border-radius: var(--basic-border-radius); + + &:hover { + border: var(--basic-border-hover); + } + + &:focus { + border: var(--basic-border-focused); + + outline: none; + } + + min-width: 0; + width: 100%; + height: 100%; + } + + .search-icon { + position: absolute; + right: 0.5em; + top: 50%; + transform: translateY(-50%); + color: #999; + z-index: 1; + } +} + +fancy-tree { + --anim-speed: 0.1s; + + background-color: #222; + + height: 100%; + display: flex; + flex-direction: column; + + &:focus { + outline: none; + } + + fancy-wrapper { + display: block; + width: 100%; + flex: 1 1; + min-width: 100px; + overflow-x: clip; + overflow-y: auto; + + fancy-tree-item { + --depth: 0; + + display: flex; + + flex-direction: column; + + &.hide-search { + overflow: hidden; + height: 0px; + display: none; + } + + fancy-tree-children { + height: 0px; + overflow: hidden; + display: none; + } + + &.open > fancy-tree-children { + display: block; + height: auto; + + &:has(fancy-tree-name:focus) { + overflow: visible; + } + } + + &.open>fancy-tree-header .caret::before { + transform: rotate(90deg); + } + + // Select if we have a children that is selected + &:has(fancy-tree-children .selected) > fancy-tree-header { + fancy-tree-name { + text-decoration: underline var(--selection); + } + } + + --highlight: 0; + &.current:not(.selected) { + --highlight: 10; + } + + &.selected > fancy-tree-header { + color: white; + --background: var(--selection); + } + + box-sizing: border-box; + + + fancy-tree-header { + padding-left: calc((var(--depth) - 1) * 1.0em + 0.25em); + position: relative; + box-sizing: border-box; + + height: 20px; + max-height: 20px; + min-height: 20px; + + --background: #222; + background-color: e("hsl(from var(--background) h s calc(var(--highlight) + l))"); + + + display: flex; + + position: relative; + + &:has(>.caret:hover, >fancy-tree-name:hover) { + --highlight: 10; + } + + fancy-tree-icon { + align-self: center; + width: 16px; + height: 16px; + + display: block; + + flex-shrink: 0; + + filter: drop-shadow(1px 1px rgba(0,0,0,0.5)); + + &.caret { + width: 16px; + height: 16px; + &::before { + content: ""; + position: absolute; + top: 0; left: 0; + margin: 2px; + width: 12px; height: 12px; + + transition: transform var(--anim-speed); + background-color: currentColor; + mask-image: url("res/icons/svg/right_caret.svg"); + + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + + pointer-events: all; + cursor: pointer; + } + } + } + + fancy-tree-name { + display: block; + text-overflow: ellipsis; + align-self: stretch; + overflow: clip; + text-wrap: nowrap; + flex-shrink: 1; + cursor: pointer; + filter: drop-shadow(1px 1px rgba(0,0,0,0.5)); + min-width: 2em; + padding: 1px; + padding-left: 0; + align-content: center; + + &:focus { + z-index: 100; + background-color: #222; + outline-offset: 1px; + } + + } + + &.feedback-drop-top::after { + position: absolute; + content: ""; + display: block; + left: 0; + right: 0; + top: 0; + border-top: solid 1px var(--selection); + z-index: 10; + pointer-events: none; + } + + &.feedback-drop-bot::after { + position: absolute; + content: ""; + display: block; + left: 0; + right: 0; + bottom: 0; + border-top: solid 1px var(--selection); + z-index: 10; + pointer-events: none; + } + + &.feedback-drop-in::after { + position: absolute; + content: ""; + display: block; + left: 0; + right: 0; + bottom: 0; + top: 0; + border: solid 1px var(--selection); + z-index: 10; + pointer-events: none; + } + } + + + + } + } + + +} + +fancy-tree2 { + --anim-speed: 0.1s; + + background-color: #222; + + height: 100%; + display: flex; + flex-direction: column; + + &:focus { + outline: none; + } + + 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: layout paint size style; + } + + fancy-tree-item { + position: absolute; + --depth: 0; + --highlight: 0; + width: 100%; + + display: flex; + + &.hide-search { + overflow: hidden; + height: 0px; + display: none; + } + + &.open> .caret::before { + transform: rotate(90deg); + } + + // Select if we have a children that is selected + &:has(fancy-tree-children .selected) > fancy-tree-header { + fancy-tree-name { + text-decoration: underline var(--selection); + } + } + + // &.current:not(.selected) { + // //--highlight: 10; + // } + + &.current { + outline: dashed 1px #AAA; + z-index: 10; + outline-offset: -1px; + } + + &.selected { + color: white; + --background: var(--selection); + } + + + + box-sizing: border-box; + + padding-left: calc((var(--depth) - 1) * 1.0em + 0.25em); + box-sizing: border-box; + + height: 20px; + max-height: 20px; + min-height: 20px; + + --background: #222; + background-color: e("hsl(from var(--background) h s calc(var(--highlight) + l))"); + + display: flex; + + &:has(>:hover) { + --highlight: 10; + cursor: pointer; + } + + .hidden { + visibility: hidden; + pointer-events: none; + } + + fancy-tree-icon { + align-self: center; + width: 16px; + height: 16px; + + display: block; + + flex-shrink: 0; + + filter: drop-shadow(1px 1px rgba(0,0,0,0.5)); + + &.caret { + width: 16px; + height: 16px; + &::before { + content: ""; + position: absolute; + top: 0; left: 0; + margin: 2px; + width: 12px; height: 12px; + + transition: transform var(--anim-speed); + background-color: currentColor; + mask-image: url("res/icons/svg/right_caret.svg"); + + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + + pointer-events: all; + } + } + } + + fancy-tree-name { + display: block; + text-overflow: ellipsis; + align-self: stretch; + overflow: clip; + text-wrap: nowrap; + flex-shrink: 1; + filter: drop-shadow(1px 1px rgba(0,0,0,0.5)); + min-width: 2em; + padding: 1px; + padding-left: 0; + align-content: center; + + &:focus { + z-index: 100; + background-color: #222; + outline-offset: 1px; + } + + >span.search-hl { + background-color: var(--selection); + color: white; + } + + } + + &.feedback-drop-top::after { + position: absolute; + content: ""; + display: block; + left: 0; + right: 0; + top: 0; + border-top: solid 1px var(--selection); + z-index: 10; + pointer-events: none; + } + + &.feedback-drop-bot::after { + position: absolute; + content: ""; + display: block; + left: 0; + right: 0; + bottom: 0; + border-top: solid 1px var(--selection); + z-index: 10; + pointer-events: none; + } + + &.feedback-drop-in::after { + position: absolute; + content: ""; + display: block; + left: 0; + right: 0; + bottom: 0; + top: 0; + border: solid 1px var(--selection); + z-index: 10; + pointer-events: none; + } + + + + } + } +} + +fancy-gallery { + display: flex; + flex-direction: column; + overflow: none; + + flex-grow: 0; + flex-shrink: 1; + align-self: stretch; + + min-width: 0; + min-height: 0; + + width: 100%; + + &,* { + box-sizing: border-box; + } + + fancy-scroll { + flex: 1 1; + overflow-y: scroll; + + fancy-item-container { + position: relative; + display: block; + overflow: hidden; + + width: 100%; + min-height: 100%; + + contain: layout paint size style; + + fancy-item { + padding: 4px; + position: absolute; + align-items: stretch; + overflow: hidden; + + contain: layout paint size style; + + background-color: #282828; + border-radius: 6px; + + &:hover { + background-color: var(--hover); + } + + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.4); + + &.details { + padding: 0; + display: flex; + align-items: center; + + box-shadow: unset; + border-radius: unset; + } + + fancy-thumbnail { + flex: 0 1; + display: block; + width: 100%; + height: 100%; + } + + .thumb { + transform: unset; + + } + + .loading { + animation: spinner 5.0s linear infinite; + transform: rotate(0deg); + @keyframes spinner { + to { transform: rotate(360deg); } + } + } + + &.details fancy-image { + height: 100%; + width: auto; + aspect-ratio: 1 / 1; + } + + fancy-name { + position: absolute; + bottom: 0px; + left: 0px; + right: 0px; + height: 32px; + display: block; + text-align: center; + //text-wrap: wrap; + //word-break: break-all; + text-overflow: ellipsis; + overflow: hidden; + + } + + &.details fancy-name { + position: relative; + height: auto; + flex: 1 1; + text-align: left; + + } + } + } + } +} + +fancy-image { + display: block; + width: 100%; + aspect-ratio: 1 / 1; + background-position: center; + background-repeat: no-repeat; + background-size: cover; + transform: scale(0.75,0.75); } \ No newline at end of file diff --git a/common.hxml b/common.hxml index cc3c9fe0b..0f3dcb263 100644 --- a/common.hxml +++ b/common.hxml @@ -5,6 +5,7 @@ -lib hx3compat -lib domkit -lib hashlink +-lib format -D js-classic -D js-unflatten -D hscriptPos diff --git a/hide/Ide.hx b/hide/Ide.hx index a98ab0d69..5e62221cc 100644 --- a/hide/Ide.hx +++ b/hide/Ide.hx @@ -28,7 +28,6 @@ class Ide extends hide.tools.IdeData { var layout : golden.Layout; var currentLayout : { name : String, state : Config.LayoutState }; - var defaultLayout : { name : String, state : Config.LayoutState }; var currentFullScreen(default,set) : hide.ui.View; var maximized : Bool; var fullscreen : Bool; @@ -40,6 +39,7 @@ class Ide extends hide.tools.IdeData { var subView : { component : String, state : Dynamic, events : {} }; var scripts : MapVoid>> = new Map(); var hasReloaded = false; + public var thumbnailMode : Bool = false; var hideRoot : hide.Element; var statusBar : hide.Element; @@ -60,8 +60,14 @@ class Ide extends hide.tools.IdeData { initPad(); isCDB = Sys.getEnv("HIDE_START_CDB") == "1" || nw.App.manifest.name == "CDB"; isDebugger = Sys.getEnv("HIDE_DEBUG") == "1"; + + var thumb = StringTools.contains(js.Browser.window.location.href, "thumbnail"); + if (thumb) { + thumbnailMode = true; + } + function wait() { - if( monaco.ScriptEditor == null ) { + if( monaco.ScriptEditor == null && !thumbnailMode ) { haxe.Timer.delay(wait, 10); return; } @@ -83,6 +89,10 @@ class Ide extends hide.tools.IdeData { hxd.Pad.wait((p) -> gamePad = p); } + function thumbnailInit() { + var generator = @:privateAccess new hide.tools.ThumbnailGenerator(); + } + function startup() { inst = this; window = nw.Window.get(); @@ -130,7 +140,9 @@ class Ide extends hide.tools.IdeData { } } } - window.show(true); + + if (!thumbnailMode) + window.show(true); if( config.global.get("hide") == null ) error("Failed to load defaultProps.json"); @@ -142,6 +154,13 @@ class Ide extends hide.tools.IdeData { setProject(current); loadProject(); + + if (thumbnailMode) { + thumbnailInit(); + hxd.System.setLoop(mainLoop); + return; + } + window.window.document.addEventListener("mousedown", function(e) { mouseX = e.x; mouseY = e.y; @@ -162,14 +181,17 @@ class Ide extends hide.tools.IdeData { }); window.on('move', function() haxe.Timer.delay(onWindowChange,100)); window.on('resize', function() haxe.Timer.delay(onWindowChange,100)); - window.on('close', function() { - if( hasReloaded ) return; - if( !isDebugger ) - for( v in views ) - if( !v.onBeforeClose() ) - return; - window.close(true); - }); + if (!thumbnailMode) { + window.on('close', function() { + if( hasReloaded ) return; + if( !isDebugger ) + for( v in views ) + if( !v.onBeforeClose() ) + return; + window.close(true); + }); + } + window.on("blur", function() { if( h3d.Engine.getCurrent() != null && !hasReloaded ) hxd.Key.initialize(); }); // handle commandline parameters @@ -214,7 +236,6 @@ class Ide extends hide.tools.IdeData { } body.ondragover = function(e:js.html.DragEvent) { dragFunc(false, e); - return false; }; body.ondrop = function(e:js.html.DragEvent) { if(!dragFunc(true, e)) { @@ -222,7 +243,6 @@ class Ide extends hide.tools.IdeData { openFile(Reflect.field(f,"path")); e.preventDefault(); } - return false; } if( subView != null ) body.className +=" hide-subview"; @@ -336,6 +356,61 @@ class Ide extends hide.tools.IdeData { v.onResize(); } + function getOrInitTarget(position: hide.ui.View.DisplayPosition) : golden.ContentItem { + if (layout.root == null) + return null; + var target = layout.root.getItemsById(position)[0]; + if (target != null) + return target; + + var parent : golden.ContentItem = null; + var config : golden.Config.ItemConfig; + var index : Int = null; + var rootRow = layout.root.contentItems[0]; + switch(position) { + case Left: + config = { + type: Stack, + }; + parent = rootRow; + index = 0; + case Center: + config = { + type: Stack, + isClosable: false, + width: 1500, + height: 800, + }; + parent = getOrInitTarget(MiddleColumnInternal); + index = 0; + case Bottom: + config = { + type: Stack, + }; + parent = getOrInitTarget(MiddleColumnInternal); + index = parent.contentItems.length; + case Right: + config = { + type: Stack, + }; + parent = rootRow; + index = parent.contentItems.length; + case MiddleColumnInternal: + config = { + type: Column, + isClosable: false, + } + parent = rootRow; + index = hxd.Math.iclamp(1, 0, parent.contentItems.length); + } + + config.id = position; + parent.addChild(config, index); + var target = layout.root.getItemsById(position)[0]; + + return target; + } + function initLayout( ?state : { name : String, state : Config.LayoutState } ) { initializing = true; @@ -344,30 +419,37 @@ class Ide extends hide.tools.IdeData { layout = null; } - defaultLayout = null; - var layoutName = isCDB ? "CDB" : "Default"; var emptyLayout : Config.LayoutState = { content : [], fullScreen : null }; - for( p in projectConfig.layouts ) - if( p.name == layoutName ) { - if( p.state.content == null ) continue; // old version - defaultLayout = p; - break; + + if( state == null ) { + var emptyLayout : Config.LayoutState = { + content: [{type: golden.Config.ItemType.Row, isClosable: false, id: "content_root"}], fullScreen : null, + }; + + + var layoutName = isCDB ? "CDB" : "Default"; + for( i => p in projectConfig.layouts.copy() ) { + if( p.name == layoutName ) { + if( p.state.content == null || (p.state.content:Array)[0]?.id != "content_root") { + projectConfig.layouts.splice(i, 1); + continue; + }; + state = p; + } + } + + if( state == null ) { + state = { name : layoutName, state : emptyLayout }; + projectConfig.layouts.push(state); } - if( defaultLayout == null ) { - defaultLayout = { name : layoutName, state : emptyLayout }; - projectConfig.layouts.push(defaultLayout); - config.current.sync(); - config.user.save(); } - if( state == null ) - state = defaultLayout; if( subView != null ) state = { name : "SubView", state : emptyLayout }; - this.currentLayout = state; + currentLayout = state; - var config : golden.Config = { + var goldenConfig : golden.Config = { content: state.state.content, settings: { // Default to false @@ -377,17 +459,18 @@ class Ide extends hide.tools.IdeData { showMaximiseIcon : config.user.get('layout.showMaximiseIcon') == true } }; + var comps = new Map(); for( vcl in hide.ui.View.viewClasses ) comps.set(vcl.name, true); function checkRec(i:golden.Config.ItemConfig) { - if( i.componentName != null && !comps.exists(i.componentName) ) { + if( i.componentName != null && i.componentState != null && !comps.exists(i.componentName) ) { i.componentState.deletedComponent = i.componentName; i.componentName = "hide.view.Unknown"; } if( i.content != null ) for( i in i.content ) checkRec(i); } - for( i in config.content ) checkRec(i); + for( i in goldenConfig.content ) checkRec(i); if (hideRoot != null) { hideRoot.remove(); @@ -405,7 +488,10 @@ class Ide extends hide.tools.IdeData { new Element('hide $commitHash').appendTo(statusBar); } - layout = new golden.Layout(config, goldenContainer.get(0)); + layout = new golden.Layout(goldenConfig, goldenContainer.get(0)); + + + var resizeTimer : haxe.Timer = null; var observer = new hide.comp.ResizeObserver((elts, observer) -> { if (resizeTimer != null) { @@ -439,6 +525,8 @@ class Ide extends hide.tools.IdeData { layout.init(); layout.on('stateChanged', onLayoutChanged); + getOrInitTarget(Center); + var waitCount = 0; function waitInit() { waitCount++; @@ -528,7 +616,7 @@ class Ide extends hide.tools.IdeData { function onLayoutChanged() { if( initializing || !ideConfig.autoSaveLayout || isCDB ) return; - defaultLayout.state = saveLayout(); + currentLayout.state = saveLayout(); if( subView == null ) this.config.user.save(); } @@ -711,6 +799,9 @@ class Ide extends hide.tools.IdeData { } h3d.mat.MaterialSetup.current = render; + if (thumbnailMode) { + return; + } initMenu(); initLayout(); }); @@ -798,6 +889,7 @@ class Ide extends hide.tools.IdeData { public function reload() { hasReloaded = true; fileWatcher.dispose(); + hide.tools.FileManager.onBeforeReload(); hide.view.RemoteConsoleView.onBeforeReload(); js.Browser.location.reload(); } @@ -1523,11 +1615,29 @@ class Ide extends hide.tools.IdeData { if( subView != null ) Reflect.callMethod(subView.events,Reflect.field(subView.events,name),[param]); } + public function getOrOpenInspector() { + var inspector = layout.root.getItemsById("inspector")[0]; + if (inspector != null) + return; + + open("hide.view.Inspector", {}); + return; + } + + public function closeInspector() { + var inspector = layout.root.getItemsById("inspector")[0]; + if (inspector != null) { + inspector.remove(); + } + } + public function open( component : String, state : Dynamic, ?onCreate : hide.ui.View -> Void, ?onOpen : hide.ui.View -> Void ) { + if (layout.root == null) + return; if( state == null ) state = {}; - var c = hide.ui.View.viewClasses.get(component); - if( c == null ) + var viewConfig = hide.ui.View.viewClasses.get(component); + if( viewConfig == null ) throw "Unknown component " + component; state.componentName = component; @@ -1540,27 +1650,10 @@ class Ide extends hide.tools.IdeData { } } - var options = c.options; + var options = viewConfig.options; - var bestTarget : golden.Container = null; - for( v in views ) - if( v.defaultOptions.position == options.position ) { - if( bestTarget == null || bestTarget.width * bestTarget.height < v.container.width * v.container.height ) - bestTarget = v.container; - } + var target = getOrInitTarget(options.position ?? Center); - var index : Null = null; - var target; - if( bestTarget != null ) - target = bestTarget.parent.parent; - else { - target = layout.root.contentItems[0]; - if( target == null ) { - layout.root.addChild({ type : Row, isClosable: false }); - target = layout.root.contentItems[0]; - } - target.config.isClosable = false; - } var needResize = options.width != null; target.on("componentCreated", function(c) { target.off("componentCreated"); @@ -1584,15 +1677,14 @@ class Ide extends hide.tools.IdeData { var config : golden.Config.ItemConfig = { type : Component, componentName : component, - componentState : state + componentState : state, }; - if( options.position == Left ) index = 0; + if (options.id != null) { + config.id = options.id; + } - if( index == null ) - target.addChild(config); - else - target.addChild(config, index); + target.addChild(config); } public function reopenLastClosedTab() { diff --git a/hide/comp/ContentEditable.hx b/hide/comp/ContentEditable.hx index 9fef88af9..c452bb672 100644 --- a/hide/comp/ContentEditable.hx +++ b/hide/comp/ContentEditable.hx @@ -5,6 +5,7 @@ class ContentEditable extends Component { public var spellcheck(never, set) : Bool; var html : js.html.Element = null; + var initialValue: String; public function new(?parent : Element, ?element : Element) { if (element == null) { @@ -22,11 +23,16 @@ class ContentEditable extends Component { var sel = js.Browser.window.getSelection(); sel.removeAllRanges(); sel.addRange(range); + initialValue = value; } html.onkeydown = function(e: js.html.KeyboardEvent) { if (e.keyCode == 13) { html.blur(); } + if (e.key == "Escape") { + value = initialValue; + html.blur(); + } e.stopPropagation(); } html.oninput = function(e) { @@ -49,10 +55,13 @@ class ContentEditable extends Component { html.onblur = function() { if (js.Browser.window.getSelection != null) {js.Browser.window.getSelection().removeAllRanges();} - if (wasEdited) { + if (get_value() != initialValue) { onChange(get_value()); wasEdited = false; } + else { + onCancel(); + } } } @@ -69,5 +78,7 @@ class ContentEditable extends Component { return html.innerText; } + public dynamic function onCancel() {}; + public dynamic function onChange(v: String) {}; } \ No newline at end of file diff --git a/hide/comp/FancyClosable.hx b/hide/comp/FancyClosable.hx new file mode 100644 index 000000000..302c1e98b --- /dev/null +++ b/hide/comp/FancyClosable.hx @@ -0,0 +1,47 @@ +package hide.comp; + +/** + A container that is hidden by default and that can appear scrolling down from the top, + like for a search bar +**/ +class FancyClosable extends hide.comp.Component { + var open = false; + + public override function new(parent: Element = null, target: Element = null) { + var el = new hide.Element(' + + + + '); + if (target != null) { + var children = target.children(); + target.replaceWith(el); + children.insertBefore(el.children().first()); + } + super(parent, el); + + var close = element.get(0).querySelector(".close-btn"); + close.onclick = (e) -> toggleOpen(false); + } + + public dynamic function onOpen() : Void {}; + public dynamic function onClose() : Void {}; + + public function toggleOpen(?force: Bool) : Void { + var want = force != null ? force : !open; + if (open != want) { + open = want; + FancyTree.animateReveal(element.get(0), open); + } + + if (open) { + onOpen(); + } else { + onClose(); + } + } + + public function isOpen() : Bool { + return open; + } +} \ No newline at end of file diff --git a/hide/comp/FancyGallery.hx b/hide/comp/FancyGallery.hx new file mode 100644 index 000000000..3e7c76c10 --- /dev/null +++ b/hide/comp/FancyGallery.hx @@ -0,0 +1,303 @@ +package hide.comp; + +enum GalleryRefreshFlag { + Search; + Items; + RegenHeader; +} + +typedef GalleryRefreshFlags = haxe.EnumFlags; + +typedef GalleryItemData = {item: GalleryItem, name: String, element: js.html.Element, iconStringCache: String}; + +class FancyGallery extends hide.comp.Component { + + var currentData : Array> = []; + var itemMap : Map<{}, GalleryItemData> = []; + var itemContainer : js.html.Element; + var scroll : js.html.Element; + + var lastHeight : Float = 0; + + var itemHeightPx = 128; + var itemWidthPx = 128; + static final itemTitleHeight = 32; + + var details = false; + + public function new(parent: Element, el: Element) { + if (el != null) { + if (el.get(0).tagName != "FANCY-GALLERY") { + throw "el must be a fancy-gallery node"; + } + } + else { + el = new Element(''); + } + + super(parent, el); + element.html(" + + + + + "); + + var resizeObserver = new hide.comp.ResizeObserver((_, _) -> { + queueRefresh(); + }); + resizeObserver.observe(element.get(0)); + + itemContainer = el.find("fancy-item-container").get(0); + + itemContainer.onwheel = (e: js.html.WheelEvent) -> { + if (e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + + + if (e.deltaY < 0) { + if (details) { + itemWidthPx = 32; + details = false; + } else { + itemWidthPx = hxd.Math.floor(itemWidthPx * 1.25); + } + } else if (e.deltaY > 0) { + itemWidthPx = hxd.Math.floor(itemWidthPx / 1.25); + } + + if (itemWidthPx < 32) { + details = true; + } + queueRefresh(); + } + } + scroll = el.find("fancy-scroll").get(0); + + scroll.onscroll = (e) -> queueRefresh(); + } + + var refreshQueued : Bool = false; + var currentRefreshFlags : GalleryRefreshFlags = GalleryRefreshFlags.ofInt(0); + + public function queueRefresh(flag: GalleryRefreshFlag = null) { + if (flag != null) { + currentRefreshFlags.set(flag); + } + if (!refreshQueued) { + refreshQueued = true; + js.Browser.window.requestAnimationFrame((_) -> onRefreshInternal()); + } + } + + public dynamic function getItems() : Array { + return []; + } + + public dynamic function getName(item: GalleryItem) : String { + return ""; + } + + public dynamic function getIcon(item: GalleryItem) : String { + return null; + } + + public dynamic function onDoubleClick(item: GalleryItem) : Void { + } + + /** + Called when an item becomes visible on screen due to scrolling or other things. + **/ + public dynamic function visibilityChanged(item: GalleryItem, isVisible: Bool) : Void { + + } + + /** + Drag and drop interface. + Set this struct with all of it's function callback to handle drag and drop inside your tree. + **/ + public var dragAndDropInterface : + { + /** + Called when the user starts a drag and drop operation on `item`. + Fill dataTransfer with the information you want to transfer, you can use getSelectedItems to handle dragging more than + one item at a time. + Return `true` if the drag operation is allowed, and `false` to cancel it + **/ + onDragStart: (item: GalleryItem, dataTransfer: js.html.DataTransfer) -> Bool, + + // /** + // Called when the user hovers on `target` with a drag and drop operation. You need to return what drop orperation is allowed + // on the given object + // **/ + // getItemDropFlags: (target: TreeItem, dataTransfer: js.html.DataTransfer) -> DropFlags, + + // /** + // Called when the user drops an item on `target` and getItemDropFlags returned at least one valid flag. + // `where` tells you where the item was dropped, and you can use `dataTransfer` to know what was dropped + // **/ + // onDrop: (target: TreeItem, where: DropOperation, dataTransfer: js.html.DataTransfer) -> Void + } = null; + + + + public function rebuild() { + queueRefresh(Items); + queueRefresh(Search); + queueRefresh(RegenHeader); + } + + /** + Never call this directly + **/ + function onRefreshInternal() { + refreshQueued = false; + + if (currentRefreshFlags.has(Items)) { + rebuildItems(); + } + + var oldChildren = [for (node in itemContainer.childNodes) node]; + + var margin = 8; + + var bounds = scroll.getBoundingClientRect(); + + + if (details) { + margin = 0; + itemHeightPx = 16; + itemWidthPx = hxd.Math.floor(bounds.width); + } else { + itemHeightPx = itemWidthPx + itemTitleHeight; + } + + + var numData = currentData.length; + + + var itemsPerRow = hxd.Math.imax(hxd.Math.floor((bounds.width - margin) / (itemWidthPx + margin)), 1); + + var height = hxd.Math.ceil(numData / itemsPerRow) * (itemHeightPx + margin) + margin; + if (height != lastHeight) { + itemContainer.style.height = '${height}px'; + lastHeight = height; + } + + // We might need to recompute the scroll height so we call getBoundingClientRect again + var scrollHeight = scroll.getBoundingClientRect().height; + + + + var clipStart = scroll.scrollTop; + var clipEnd = scrollHeight + clipStart; + var itemStart = hxd.Math.floor((clipStart-margin) / (itemHeightPx+margin)) * itemsPerRow; + var itemEnd = hxd.Math.ceil((clipEnd-margin) / (itemHeightPx+margin)) * itemsPerRow; + + + for (index in hxd.Math.imax(itemStart, 0) ... hxd.Math.imin(currentData.length, itemEnd)) { + var data = currentData[index]; + var element = getElement(data); + + element.style.left = '${((index % itemsPerRow)) * (itemWidthPx + margin) + margin}px'; + element.style.top = '${hxd.Math.floor(index / itemsPerRow) * (itemHeightPx + margin) + margin}px'; + + if (!oldChildren.remove(element)) { + itemContainer.appendChild(element); + visibilityChanged(data.item, true); + } + } + + for (oldChild in oldChildren) { + if (itemContainer.contains(oldChild)) { + itemContainer.removeChild(oldChild); + var data : GalleryItemData = untyped oldChild.__data; + if (data != null) { + visibilityChanged(data.item, false); + } + } + } + + currentRefreshFlags = GalleryRefreshFlags.ofInt(0); + } + + function setupDragAndDrop(data : GalleryItemData) { + if (dragAndDropInterface == null) + return; + + data.element.draggable = true; + data.element.ondragstart = (e:js.html.DragEvent) -> { + if (dragAndDropInterface.onDragStart(data.item, e.dataTransfer)) { + e.dataTransfer.effectAllowed = "move"; + e.dataTransfer.setDragImage(data.element, 0,0); + } else { + e.preventDefault(); + } + } + } + + function getElement(data : GalleryItemData) : js.html.Element { + if (currentRefreshFlags.has(RegenHeader) && data.element != null) { + data.element.remove(); + data.element = null; + data.iconStringCache = null; + } + + if (data.element == null) { + data.element = js.Browser.document.createElement("fancy-item"); + untyped data.element.__data = data; + + data.element.innerHTML = ' + + + '; + + data.element.ondblclick = (e) -> { + onDoubleClick(data.item); + } + + setupDragAndDrop(data); + } + + if (!details) { + data.element.style.width = '${itemWidthPx}px'; + data.element.style.height = '${itemHeightPx}px'; + } else { + data.element.style.width = '100%'; + } + + data.element.style.height = '${itemHeightPx}px'; + data.element.classList.toggle("details", details); + + var name = data.element.querySelector("fancy-name"); + if (name.title != data.name) { + name.innerHTML = '${data.name}'; + name.title = data.name; + } + + var img = data.element.querySelector("fancy-thumbnail"); + var imgString = getIcon(data.item) ?? ''; + if (imgString != data.iconStringCache) { + img.innerHTML = imgString; + data.iconStringCache = imgString; + } + + return data.element; + } + + function rebuildItems() { + currentData.resize(0); + var items = getItems(); + for (item in items) { + var data = hrt.tools.MapUtils.getOrPut(itemMap, cast item, { + item: item, + name: getName(item), + element: null, + iconStringCache: null, + }); + + currentData.push(data); + } + } +} \ No newline at end of file diff --git a/hide/comp/FancySearch.hx b/hide/comp/FancySearch.hx new file mode 100644 index 000000000..9f60e39a9 --- /dev/null +++ b/hide/comp/FancySearch.hx @@ -0,0 +1,51 @@ +package hide.comp; + +typedef SearchRanges = Array; +class FancySearch extends hide.comp.Component { + var open = false; + var input : js.html.InputElement; + + public override function new(parent: Element = null, target: Element = null) { + var el = new hide.Element(' + + + + + '); + if (target != null) { + target.replaceWith(el); + } + super(parent, el); + + input = cast element.get(0).querySelector(".search-field"); + + input.oninput = (e) -> { + onSearch(e.target.value, false); + } + + input.onchange = (e) -> { + onSearch(e.target.value, true); + } + } + + public dynamic function onSearch(search: String, enter: Bool) : Void {}; + + public function hasFocus() : Bool { + return js.Browser.document.activeElement == input; + } + + public function blur() : Void { + input.blur(); + } + + public function focus() : Void { + input.focus(); + } + + public static function computeSearchRanges(haystack: String, needle: String) : SearchRanges { + var pos = haystack.toLowerCase().indexOf(needle); + if (pos < 0) + return null; + return [pos, pos + needle.length]; + } +} \ No newline at end of file diff --git a/hide/comp/FancyTree.hx b/hide/comp/FancyTree.hx new file mode 100644 index 000000000..1a85f6ed6 --- /dev/null +++ b/hide/comp/FancyTree.hx @@ -0,0 +1,802 @@ +package hide.comp; + +enum DropFlag { + Reorder; + Reparent; +} + +typedef DropFlags = haxe.EnumFlags; + +enum RefreshFlag { + Flat; + Search; + FocusCurrent; + RegenHeader; +} + +typedef RefreshFlags = haxe.EnumFlags; + +enum FilterFlag { + Visible; + MatchSearch; + Open; +} + +typedef FilterFlags = haxe.EnumFlags; + +typedef TreeItemData = {element: js.html.Element, ?searchRanges: FancySearch.SearchRanges, item: TreeItem, name: String, ?iconCache: String, open: Bool, filterState: FilterFlags, children: Array>, parent: TreeItemData, depth: Int, identifier: String}; + +enum DropOperation { + Before; + After; + Inside; +} + +class FancyTree extends hide.comp.Component { + var rootData : Array> = []; + var itemMap : Map<{}, TreeItemData> = []; + var selection : Map<{}, Bool> = []; + var openState: Map = []; + var currentItem(default, set) : TreeItemData; + var currentVisible(default, set) : Bool = false; + + static final overDragOpenDelaySec = 0.5; + + function set_currentVisible(v) { + currentVisible = v; + if (currentVisible) + queueRefresh(FocusCurrent); + else + queueRefresh(); + return currentVisible; + } + + function set_currentItem(v) { + currentItem = v; + queueRefresh(FocusCurrent); + return currentItem; + } + + var searchBarClosable: hide.comp.FancyClosable = null; + var searchBar : hide.comp.FancySearch = null; + + var flatData : Array>; + + var itemContainer : js.html.Element; + var scroll : js.html.Element; + var currentSearch : String = ""; + + var moveLastDragOver: TreeItemData; + var moveLastDragOverStart: Float = 0; + + + public function new(parent: Element) { + var el = new Element(' + + + + + + + ' + ); + super(parent, el); + + searchBarClosable = new FancyClosable(null, element.find("fancy-closable")); + + searchBar = new FancySearch(null, element.find("fancy-search")); + searchBar.onSearch = (search, _) -> { + currentSearch = search.toLowerCase(); + queueRefresh(Search); + } + + searchBarClosable.onClose = () -> { + currentSearch = ""; + queueRefresh(Search); + } + + scroll = el.find("fancy-scroll").get(0); + itemContainer = el.find("fancy-item-container").get(0); + lastHeight = null; + + var fancyTree = el.get(0); + fancyTree.onkeydown = inputHandler; + + scroll.onscroll = (e) -> queueRefresh(); + + fancyTree.onblur = (e) -> { + currentVisible = false; + currentItem = null; + } + + fancyTree.onclick = (e) -> { + currentVisible = false; + } + } + + + /** + To customise the icon of an element + **/ + public dynamic function getIcon(item: TreeItem) : String {return null;} + + /** + The display name of an item + **/ + public dynamic function getName(item: TreeItem) : String {return "undefined";} + + /** + If items in the tree can have the same name, this function should return a unique name for each of them. + Used to save the state of the open folders in the tree + **/ + public dynamic function getUniqueName(item: TreeItem) : String {return getName(item);} + + /** + Called when the selected items in the tree changed + **/ + public dynamic function onSelectionChanged() { + } + + /** + Drag and drop interface. + Set this struct with all of it's function callback to handle drag and drop inside your tree. + **/ + public var dragAndDropInterface : + { + /** + Called when the user starts a drag and drop operation on `item`. + Fill dataTransfer with the information you want to transfer, you can use getSelectedItems to handle dragging more than + one item at a time. + Return `true` if the drag operation is allowed, and `false` to cancel it + **/ + onDragStart: (item: TreeItem, dataTransfer: js.html.DataTransfer) -> Bool, + + /** + Called when the user hovers on `target` with a drag and drop operation. You need to return what drop orperation is allowed + on the given object + **/ + getItemDropFlags: (target: TreeItem, dataTransfer: js.html.DataTransfer) -> DropFlags, + + /** + Called when the user drops an item on `target` and getItemDropFlags returned at least one valid flag. + `where` tells you where the item was dropped, and you can use `dataTransfer` to know what was dropped + **/ + onDrop: (target: TreeItem, where: DropOperation, dataTransfer: js.html.DataTransfer) -> Void + } = null; + + /** + Called when the user renamed the item via F2 / Context menu + **/ + public dynamic function onNameChange(item: TreeItem, newName: String) : Void { + } + + /** + Called for each of your items in the tree. for the root elements, get called with null as a parameter + **/ + public dynamic function getChildren(item: TreeItem) : Array {return null;} + + /** + Returns a string that allow an item in the tree to be uniquely identified. + Default to a path/of/the/item/name + Customize this if you have items that can share names + **/ + public dynamic function getIdentifier(item: TreeItem) : String { + var data = itemMap.get(cast item); + if (data == null) + return null; + function rec(data) { + if (data.parent != null) + return getIdentifier(data.parent) + "/" + data.name; + return data.name; + } + + return rec(data); + } + + /** + Called to know if an item in the tree can be opened or has children. Default to calling getChildren and seeing if it returns false. + Set this function to optimise the initial loading of the tree if getChildren is expensive + **/ + public dynamic function hasChildren(item : TreeItem) : Bool { + var children = getChildren(item); + if (children == null) + return false; + return children.length > 0; + } + + public function getSelectedItems() : Array { + return [for (item => _ in selection) (cast item:TreeItemData).item]; + } + + public function generateChildren(parentData: TreeItemData) : Array> { + var childrenTreeItem = getChildren(parentData?.item); + + var childrenData : Array> = []; + if (childrenTreeItem != null) { + for (childItem in childrenTreeItem) { + var childData : TreeItemData; + childData = { + item: childItem, + parent: parentData, + children: null, + open: false, + filterState: Visible, + depth: parentData?.depth + 1 ?? 0, + element: null, + name: StringTools.htmlEscape(getName(childItem)), + identifier: getIdentifier(childItem), + }; + itemMap.set(cast childItem, childData); + childrenData.push(childData); + } + } + if (parentData != null) { + parentData.children = childrenData; + } + return childrenData; + } + + public function ensureVisible(data) { + + } + + public function rebuildTree() { + rootData = generateChildren(null); + + queueRefresh(Search); + } + + function regenerateFlatData() { + flatData = []; + flattenRec(rootData, flatData); + } + + public function selectItem(item: TreeItem, openSelf: Bool = false) { + clearSelection(); + var data = itemMap.get(cast item); + if (data == null) { + return; + } + setSelection(data, true); + currentItem = data; + var cur = openSelf ? data : data.parent; + while (cur != null) { + toggleDataOpen(cur, true); + cur = cur.parent; + } + } + + function inputHandler(e: js.html.KeyboardEvent) { + if (hide.ui.Keys.matchJsEvent("search", e, ide.currentConfig)) { + e.stopPropagation(); + e.preventDefault(); + + searchBarClosable.toggleOpen(true); + searchBar.focus(); + } + + // if (hide.ui.Keys.matchJsEvent("rename", e, ide.currentConfig) && selection.iterator().hasNext()) { + // e.stopPropagation(); + // e.preventDefault(); + + // beginRename(cast selection.keyValueIterator().next().key); + // } + + if (e.key == "Escape") { + if (searchBarClosable.isOpen()) { + e.stopPropagation(); + e.preventDefault(); + + searchBarClosable.toggleOpen(false); + searchBar.blur(); + element.get(0).focus(); + currentSearch = ""; + queueRefresh(Search); + } + } + + var delta = 0; + switch (e.key) { + case "ArrowUp": + delta -= 1; + case "ArrowDown": + delta += 1; + case "PageUp": + delta -= 10; + case "PageDown": + delta += 10; + } + + if (delta != 0) { + e.stopPropagation(); + e.preventDefault(); + moveCurrent(delta); + return; + } + + if (currentItem == null) + return; + + if (e.key == "ArrowRight" && hasChildren(currentItem.item)) { + e.stopPropagation(); + e.preventDefault(); + + if (currentItem == null || isOpen(currentItem)) { + moveCurrent(1); + } + else if (currentItem != null && !isOpen(currentItem)) { + toggleDataOpen(currentItem, true); + //saveState(); + } + return; + } + if (e.key == "ArrowLeft") { + e.stopPropagation(); + e.preventDefault(); + + var anyChildren = hasChildren(currentItem.item); + var goToParent = !anyChildren && currentItem.parent != null; + goToParent = goToParent || anyChildren && !isOpen(currentItem); + + if (goToParent && currentItem.parent != null) { + currentItem = currentItem.parent; + } else if(anyChildren && currentItem != null) { + toggleDataOpen(currentItem, false); + //saveState(); + } + return; + } + + if (e.key == "Enter") { + e.stopPropagation(); + e.preventDefault(); + + clearSelection(); + if (currentItem != null) { + setSelection(currentItem, true); + } + onSelectionChanged(); + return; + } + } + + public function moveCurrent(delta: Int) { + if (delta == 0) + return; + if (flatData.length <= 0) + return; + + currentVisible = true; + + var currentIndex = flatData.indexOf(currentItem); + if (currentIndex < 0) { + currentItem = flatData[0]; + + if (searchBarClosable.isOpen() && searchBar.hasFocus()) { + searchBar.blur(); + element.focus(); + } + return; + } + + var nextIndex = currentIndex + delta; + if (nextIndex < 0) { + if (searchBarClosable.isOpen()) { + searchBar.focus(); + return; + } + else { + nextIndex = 0; + } + } + else { + if (searchBarClosable.isOpen() && searchBar.hasFocus()) { + searchBar.blur(); + element.focus(); + } + } + + if (nextIndex > flatData.length-1) + nextIndex = flatData.length-1; + + if (nextIndex != currentIndex) { + currentItem = flatData[nextIndex]; + } + } + + var lastHeight = null; + + // Never call this function directly, instead call queueRefresh(); + function onRefreshInternal() { + if (currentRefreshFlags.has(Search)) { + filterRec(rootData); + currentRefreshFlags.set(Flat); + } + + if (currentRefreshFlags.has(Flat) || flatData == null) { + regenerateFlatData(); + } + + //itemContainer.innerHTML = ""; + var oldChildren = [for (node in itemContainer.childNodes) node]; + + var itemHeightPx = 20; + + var height = itemHeightPx * flatData.length; + if (height != lastHeight) { + itemContainer.style.height = '${height}px'; + lastHeight = height; + } + + var scrollHeight = scroll.getBoundingClientRect().height; + + if (currentRefreshFlags.has(FocusCurrent)) { + var currentIndex = flatData.indexOf(currentItem); + + if (currentIndex >= 0) { + var currentHeight = currentIndex * itemHeightPx; + if (currentHeight < scroll.scrollTop) { + scroll.scrollTo(scroll.scrollLeft, currentHeight); + } + + if (currentHeight + itemHeightPx - scrollHeight > scroll.scrollTop) { + scroll.scrollTo(scroll.scrollLeft, currentHeight + itemHeightPx - scrollHeight); + } + } + } + + var clipStart = scroll.scrollTop; + var clipEnd = scrollHeight + clipStart; + var itemStart = hxd.Math.floor(clipStart / itemHeightPx); + var itemEnd = hxd.Math.ceil(clipEnd / itemHeightPx); + + for (index in hxd.Math.imax(itemStart, 0) ... hxd.Math.imin(flatData.length, itemEnd + 1)) { + var data = flatData[index]; + var element = genElement(data); + element.style.top = '${index * itemHeightPx}px'; + if (!oldChildren.remove(element)) + itemContainer.appendChild(element); + } + + for (oldChild in oldChildren) { + itemContainer.removeChild(oldChild); + } + + currentRefreshFlags = RefreshFlags.ofInt(0); + refreshQueued = false; + } + + public function filterRec(children: Array>) : Bool { + var anyVisible = false; + for (child in children) { + child.filterState = FilterFlags.ofInt(0); + child.searchRanges = null; + + if (currentSearch.length == 0) { + child.filterState |= Visible; + } else { + child.searchRanges = FancySearch.computeSearchRanges(child.name, currentSearch); + if (child.searchRanges != null) { + child.filterState |= MatchSearch; + child.filterState |= Visible; + } + } + if (child.children == null) { + generateChildren(child); + } + + if(filterRec(child.children) && currentSearch.length > 0) { + child.filterState |= Visible; + child.filterState |= Open; + } + + anyVisible = anyVisible || child.filterState.has(Visible); + } + + return anyVisible; + } + + function genElement(data: TreeItemData) : js.html.Element { + var element : js.html.Element = data.element; + + if (currentRefreshFlags.has(RegenHeader) && data.element != null) { + data.element.remove(); + data.element = null; + } + + if (data.element == null) { + element = js.Browser.document.createElement("fancy-tree-item"); + element.style.setProperty("--depth", Std.string(data.depth)); + + element.innerHTML = + ' + + + + '; + + var fold = element.querySelector(".caret"); + fold.addEventListener("click", (e) -> { + toggleDataOpen(data); + //saveState(); + }); + + var closure = dataClickHandler.bind(data); + + var icon = element.querySelector(".header-icon"); + icon.onclick = closure; + + var name = element.querySelector("fancy-tree-name"); + name.onclick = closure; + + data.element = element; + + setupDragAndDrop(data); + } + + var fold = element.querySelector(".caret"); + fold.classList.toggle("hidden", !hasChildren(data.item)); + element.classList.toggle("open", isOpen(data)); + element.classList.toggle("selected", selection.exists(cast data)); + element.classList.toggle("current", currentVisible && currentItem == data); + + var icon = element.querySelector(".header-icon"); + var iconContent = getIcon(data.item); + icon.classList.toggle("hidden", iconContent == null); + if (iconContent != null && iconContent != data.iconCache) { + icon.innerHTML = iconContent; + data.iconCache = iconContent; + } + + var nameElement = element.querySelector("fancy-tree-name"); + element.title = data.name; + + if (data.searchRanges != null) { + var name = data.name; + var lastPos = 0; + var finalName = ""; + for (index in 0...(data.searchRanges.length>>1)) { + var first = name.substr(lastPos, data.searchRanges[index]); + var match = name.substr(data.searchRanges[index], data.searchRanges[index+1] - data.searchRanges[index]); + finalName += first + '' + match + ""; + lastPos = data.searchRanges[index+1]; + } + finalName += name.substr(lastPos); + nameElement.innerHTML = finalName; + } else { + nameElement.innerHTML = data.name; + } + + return data.element; + } + + function setupDragAndDrop(data: TreeItemData) { + if (dragAndDropInterface != null) { + var ondragstart = (e: js.html.DragEvent) -> { + if (!selection.get(cast data)) { + clearSelection(); + setSelection(data, true); + } + + moveLastDragOver = null; + + if (dragAndDropInterface.onDragStart(data.item, e.dataTransfer)) { + e.dataTransfer.effectAllowed = "move"; + e.dataTransfer.setDragImage(data.element, 0, 0); + } else { + e.preventDefault(); + } + }; + + var elements = [data.element.querySelector("fancy-tree-name"), data.element.querySelector(".header-icon")]; + + // drag from the interactible elements of the item + for (element in elements) { + element.draggable = true; + element.ondragstart = ondragstart; + } + + + // drop on the full item element + data.element.ondragover = (e: js.html.DragEvent) -> { + var operation = getDragOperation(data,e); + if (operation != null) { + // Auto open item if the user hover for enough time + if (operation == Inside) { + var time = haxe.Timer.stamp(); + + if (moveLastDragOver != data) { + moveLastDragOver = data; + moveLastDragOverStart = haxe.Timer.stamp(); + } + + if (time - moveLastDragOverStart > overDragOpenDelaySec && !isOpen(data)) { + toggleDataOpen(data, true); + //saveState(); + } + } + + e.preventDefault(); + setDragStyle(data.element, operation); + } else { + setDragStyle(data.element, null); + } + } + + data.element.ondragenter = (e: js.html.DragEvent) -> { + var operation = getDragOperation(data,e); + if (operation != null) { + setDragStyle(data.element, operation); + e.preventDefault(); + } + } + + data.element.ondragleave = (e: js.html.DragEvent) -> { + setDragStyle(data.element, null); + e.preventDefault(); + } + + data.element.ondrop = (e: js.html.DragEvent) -> { + setDragStyle(data.element, null); + e.preventDefault(); + + var operation = getDragOperation(data,e); + if (operation != null) { + dragAndDropInterface.onDrop(data.item, operation, e.dataTransfer); + } + e.preventDefault(); + e.stopPropagation(); + } + } + } + + function setDragStyle(element: js.html.Element, target: Null) { + element.classList.toggle("feedback-drop-top", target == Before); + element.classList.toggle("feedback-drop-bot", target == After); + element.classList.toggle("feedback-drop-in", target == Inside); + } + + function getDragOperation(data: TreeItemData, event: js.html.DragEvent) : DropOperation { + var element = data.element; + var flags = dragAndDropInterface.getItemDropFlags(data.item, event.dataTransfer); + if (flags == DropFlags.ofInt(0)) { + return null; + } + + if (!flags.has(Reorder)) { + return Inside; + } + + var rect = element.getBoundingClientRect(); + var nameRect = element.getBoundingClientRect(); + + if (flags.has(Reparent) && event.clientX > nameRect.left + 100) { + return Inside; + } + + if (event.clientY > rect.top + rect.height / 2) { + return After; + } + return Before; + } + + public function clearSelection() { + selection.clear(); + queueRefresh(); + } + + + + function setSelection(data: TreeItemData, select: Bool) { + if (select) { + selection.set(cast data, true); + } else { + selection.remove(cast data); + } + } + + function dataClickHandler(data: TreeItemData, event: js.html.MouseEvent) : Void { + if (!event.ctrlKey) { + clearSelection(); + } + + var currentIndex = flatData.indexOf(currentItem); + if (event.shiftKey && currentIndex >= 0) { + var newIndex = flatData.indexOf(data); + + var min = hxd.Math.imin(currentIndex, newIndex); + var max = hxd.Math.imax(currentIndex, newIndex); + + for (i in min...max + 1) { + setSelection(flatData[i], true); + } + } else { + setSelection(data, !selection.exists(cast data)); + } + + if (!(event.shiftKey && !event.ctrlKey) || currentItem == null) + currentItem = data; + onSelectionChanged(); + + queueRefresh(); + } + + public function openItem(item: TreeItem, ?force: Bool) { + var data = itemMap.get(cast item); + if (data != null) { + toggleDataOpen(data, force); + } + } + + function toggleDataOpen(data: TreeItemData, ?force: Bool) { + var want = force ?? !isOpen(data); + if (currentSearch.length > 0) { + data.filterState.setTo(Open, want); + } + data.open = want; + queueRefresh(Flat); + } + + var refreshQueued : Bool = false; + var currentRefreshFlags : RefreshFlags = RefreshFlags.ofInt(0); + + + // TODO(ces) : The main release of haxe doesn't support type inference with `|` which make using + // queueRefresh with an EnumFlag as an argument cumbersome. Untill then, make multiple queueRefresh calls + // with each of the flags you want to set + function queueRefresh(?flag: RefreshFlag = null) { + if (flag != null) { + currentRefreshFlags.set(flag); + } + if (!refreshQueued) { + refreshQueued = true; + js.Browser.window.requestAnimationFrame((_) -> onRefreshInternal()); + } + } + + public static function animateReveal(element: js.html.Element, reveal: Bool, durationMs: Int = 75) { + function finish() { + if (reveal) { + element.style.height = "auto"; + } else { + element.style.height = null; + } + }; + for (anim in element.getAnimations()) { + anim.cancel(); + } + + if (durationMs > 0) { + var anim = element.animate([ + {height: "0px"}, + {height: '${element.scrollHeight}px'}, + ], { + duration: durationMs, + iterations: 1, + direction: reveal ? js.html.PlaybackDirection.NORMAL : js.html.PlaybackDirection.REVERSE, + easing: "ease-in", + }); + + anim.onfinish = (e) -> finish(); + } + else { + finish(); + } + } + function flattenRec(currentArray: Array>, targetArray: Array>) { + for (child in currentArray) { + if (!child.filterState.has(Visible)) continue; + targetArray.push(child); + if (isOpen(child)) { + if (child.children == null) + generateChildren(child); + flattenRec(child.children, targetArray); + } + } + } + + function isOpen(data: TreeItemData) { + return data.open || data.filterState.has(Open); + } + +} \ No newline at end of file diff --git a/hide/comp/FancyTreeOld.hx b/hide/comp/FancyTreeOld.hx new file mode 100644 index 000000000..d56e4b24e --- /dev/null +++ b/hide/comp/FancyTreeOld.hx @@ -0,0 +1,787 @@ +// package hide.comp; + +// /** +// TODO : +// [X] Search +// [X] Rename item +// [ ] Move items +// [X] Save fold state +// [ ] Customisable context menu +// [ ] Customisable end of list icons +// [ ] Custom "pills" info near the title +// [X] Fold animation +// [X] General styling + +// **/ + +// enum MoveAllowedOperations { +// Reorder; +// Reparent; +// } + +// typedef MoveFlags = haxe.EnumFlags; + +// enum GetDragTarget { +// None; +// Top; +// Bot; +// In; +// } + + +// typedef TreeItemData = {?element: js.html.Element, ?header: js.html.Element, ?children: Array, ?parent: TreeItem, ?depth: Int, ?item: TreeItem, ?temporaryOpen: Bool, ?passSearch: Bool, ?name: String, ?path: String}; + +// class FancyTree extends hide.comp.Component { +// public var itemMap : Map<{}, TreeItemData> = []; + +// // Forced to use another object because itemMap can't use null as an index for some reason +// var rootData : TreeItemData = {}; + +// var openState: Map = []; + +// public var moveFlags : MoveFlags = MoveFlags.ofInt(0); + +// var selection : Map<{}, Bool> = []; +// var currentItem : TreeItem; + +// var searchBar : hide.comp.FancySearch = null; + +// var moveLastDragOver : TreeItem; +// var moveLastDragTime : Int = 0; + +// public function new(parent: Element) { +// var el = new Element(' +// +// '); +// super(parent, el); + +// searchBar = new FancySearch(null, element.find("fancy-search")); +// searchBar.onSearch = (search, _) -> { +// filterRec(null, search); + +// ensureVisible(getDataOrRoot(currentItem)); +// } + +// var fancyTree = el.get(0); +// fancyTree.onkeydown = (e: js.html.KeyboardEvent) -> { + +// if (hide.ui.Keys.matchJsEvent("search", e, ide.currentConfig)) { +// e.stopPropagation(); +// e.preventDefault(); + +// searchBar.toggleSearch(true, true); +// } + +// if (hide.ui.Keys.matchJsEvent("rename", e, ide.currentConfig) && selection.iterator().hasNext()) { +// e.stopPropagation(); +// e.preventDefault(); + +// beginRename(cast selection.keyValueIterator().next().key); +// } + +// if (e.key == "Escape") { +// if (searchBar.isOpen()) { +// e.stopPropagation(); +// e.preventDefault(); + +// searchBar.toggleSearch(false); +// fancyTree.focus(); +// resetSearch(null); +// } +// } + +// var delta = 0; +// switch (e.key) { +// case "ArrowUp": +// delta -= 1; +// case "ArrowDown": +// delta += 1; +// case "PageUp": +// delta -= 10; +// case "PageDown": +// delta += 10; +// } + +// if (delta != 0) { +// e.stopPropagation(); +// e.preventDefault(); +// moveCurrent(delta); +// return; +// } + +// if (currentItem == null) +// return; + +// if (e.key == "ArrowRight" && hasChildren(currentItem)) { +// e.stopPropagation(); +// e.preventDefault(); + +// var currentData = getDataOrRoot(currentItem); +// if (currentData == null || isDataVisuallyOpen(currentData)) { +// moveCurrent(1); +// } +// else if (currentData != null && !isDataVisuallyOpen(currentData)) { +// toggleItemOpen(currentItem, true); +// saveState(); +// } +// return; +// } +// if (e.key == "ArrowLeft") { +// e.stopPropagation(); +// e.preventDefault(); + +// var currentData = getDataOrRoot(currentItem); +// if (currentData != null) { +// var anyChildren = hasChildren(currentItem); +// var goToParent = !anyChildren && currentData.parent != null; +// goToParent = goToParent || anyChildren && !isDataVisuallyOpen(currentData); + +// if (goToParent && currentData.parent != null) { +// setCurrent(currentData.parent); +// } else if(anyChildren && currentItem != null) { +// toggleItemOpen(currentItem, false); +// saveState(); +// } +// } +// return; +// } + +// if (e.key == "Enter") { +// e.stopPropagation(); +// e.preventDefault(); + +// clearSelection(); +// if (currentItem != null) { +// setSelection(currentItem, true); +// } +// onSelectionChanged(); +// return; +// } +// }; + +// resetItemCache(); +// } + +// public function beginRename(item: TreeItem) { +// var data = getDataOrRoot(item); + +// var name = data.header.querySelector("fancy-tree-name"); +// name.contentEditable = "plaintext-only"; +// var edit = new ContentEditable(null, new Element(name)); + +// edit.onChange = (newValue) -> { +// onNameChange(item, name.textContent); +// refreshHeader(data); +// element.focus(); +// } + +// edit.onCancel = () -> { +// refreshHeader(data); +// element.focus(); +// } + +// edit.element.focus(); +// } + +// function saveState() { +// saveDisplayState("openState", openState); +// } + +// inline function isDataVisuallyOpen(data: TreeItemData) { +// return data.item == null || openState.get(data.path) || data.temporaryOpen; +// } + +// /** +// Called for each of your items in the tree. for the root elements, get called with null as a parameter +// **/ +// public dynamic function getChildren(item: TreeItem) : Array {return null;} + +// /** +// Called to know if an item in the tree can be opened or has children. Default to calling getChildren and seeing if it returns false. +// Set this function to optimise the initial loading of the tree if getChildren is expensive +// **/ +// public dynamic function hasChildren(item : TreeItem) : Bool { +// var children = getChildren(item); +// if (children == null) +// return false; +// return children.length > 0; +// } + +// /** +// To customise the icon of an element +// **/ +// public dynamic function getIcon(item: TreeItem) : js.html.Element {return null;} + +// /** +// The display name of an item +// **/ +// public dynamic function getName(item: TreeItem) : String {return "undefined";} + +// /** +// If items in the tree can have the same name, this function should return a unique name for each of them. +// Used to save the state of the open folders in the tree +// **/ +// public dynamic function getUniqueName(item: TreeItem) : String {return getName(item);} + +// /** +// Called when the selected items in the tree changed +// **/ +// public dynamic function onSelectionChanged() { +// } + +// /** +// Used to filter if an item can be reparented to another. Only called if MoveFlags contains Reparent. +// If this function return false, the reparent operation between the two arguments is not allowed +// **/ +// public dynamic function canReparentTo(items: Array, newParent: TreeItem) : Bool { +// return true; +// } + +// /** +// Called to handle a reparent/reorder operation. newIndex will be -1 if the current MoveFlags don't contain can reorder, and t +// **/ +// public dynamic function onMove(items: Array, newParent: TreeItem, newIndex: Int) : Void { +// } + +// /** +// Called when the user renamed the item via F2 / Context menu +// **/ +// public dynamic function onNameChange(item: TreeItem, newName: String) : Void { +// } + +// public function getSelectedItems() : Array { +// return [for (item => _ in selection) cast item]; +// } + +// /** +// Destroy and recreate all the elements in the tree. +// All the cached children will be erased +// **/ +// public function rebuildTree() { +// resetItemCache(); +// redrawItems(); +// resetSearch(null); +// } + +// function resetItemCache() { +// itemMap.clear(); +// rootData = {element: js.Browser.document.createDivElement() /*element.find("fancy-wrapper").get(0)*/, depth: 0, path: ""}; +// openState = getDisplayState("openState") ?? openState; +// } + +// function redrawItems() { +// untyped rootData.element.replaceChildren(); +// initChildren(null); +// } + +// function toggleItemOpen(item: TreeItem, ?force: Bool, animate: Bool = true, temporary: Bool = false) : Void { + +// var data = itemMap.get(cast item); + +// var wantOpen = force ?? !isDataVisuallyOpen(data); +// var wasOpen = isDataVisuallyOpen(data); + +// data.temporaryOpen = wantOpen; +// if (!temporary) { +// openState.set(data.path, wantOpen); +// } + +// if (wantOpen) { +// initChildren(item); +// } + +// data.element.classList.toggle("open", wantOpen); +// var childrenElement = data.element.querySelector("fancy-tree-children"); +// if (animate && childrenElement != null && wantOpen != wasOpen) { +// animateReveal(childrenElement, wantOpen); +// } + +// } + +// public static function animateReveal(element: js.html.Element, reveal: Bool, durationMs: Int = 75) { +// function finish() { +// if (reveal) { +// element.style.height = "auto"; +// } else { +// element.style.height = null; +// } +// }; + +// for (anim in element.getAnimations()) { +// anim.cancel(); +// } + +// if (durationMs > 0) { +// var anim = element.animate([ +// {height: "0px"}, +// {height: '${element.scrollHeight}px'}, +// ], { +// duration: durationMs, +// iterations: 1, +// direction: reveal ? js.html.PlaybackDirection.NORMAL : js.html.PlaybackDirection.REVERSE, +// easing: "ease-in", +// }); + +// anim.onfinish = (e) -> finish(); +// } +// else { +// finish(); +// } +// } + +// function syncOpen(item: TreeItem) : Void { +// var data = getDataOrRoot(item); + +// if (item != null) +// toggleItemOpen(item, openState.get(data.path) ?? false, false, false); + +// if (data.children != null) { +// for (child in data.children) { +// syncOpen(child); +// } +// } +// } + +// function resetSearch(item: TreeItem) : Void { +// var data = getDataOrRoot(item); +// data.passSearch = true; +// data.element.classList.toggle("hide-search", !data.passSearch); +// //initChildren(item); +// if (data.children != null) { +// for (child in data.children) { +// resetSearch(child); +// } +// } +// if (resetSearch == null) +// syncOpen(null); +// } + +// static function filterMatch(haystack: String, needle: String) { +// return StringTools.contains(haystack.toLowerCase(), needle.toLowerCase()); +// } + +// /** +// Returns true if any children passes the current filter +// **/ +// function filterRec(item: TreeItem, currentFilter: String) : Bool { +// if (item == null) { +// resetSearch(null); +// } +// var data = getDataOrRoot(item); + +// data.passSearch = data.name != null ? filterMatch(data.name, currentFilter) : false; + +// var anyChildrenPass = false; +// if (!data.passSearch) { +// if (data.children == null) +// initChildren(data.item); +// if (data.children != null) { +// for (child in data.children) { +// anyChildrenPass = filterRec(child, currentFilter) || anyChildrenPass; +// } +// } +// } + +// if (anyChildrenPass) { +// data.passSearch = true; +// if (item != null) { +// toggleItemOpen(item, true, false, true); +// } +// } + +// if (item != null) { +// data.element.classList.toggle("hide-search", !data.passSearch); +// } + +// return data.passSearch; +// } + +// function initChildren(item: TreeItem) : Void { +// var data = getDataOrRoot(item); +// if (data?.element == null || data?.depth == null) +// throw "Data is not properly initialised"; + +// if (data.children == null) { +// data.children = getChildren(item); + +// if (data.children != null) { +// var childrenElement = js.Browser.document.createElement("fancy-tree-children"); +// data.element.append(childrenElement); + +// for (child in data.children) { +// if (child == null) +// continue; +// var childElem = getElement(child, item, data.depth + 1); +// if (childElem != null) +// childrenElement.append(childElem); +// } +// } +// } +// } + +// function getDataOrRoot(item: TreeItem) { +// if (item != null) { +// return hrt.tools.MapUtils.getOrPut(itemMap, cast item, {item: item}); +// } else { +// return rootData; +// } +// } + +// /** +// The type used by this tree in the drag/drop operations +// **/ +// inline public function getDragDataType() { +// return ("application/x." + saveDisplayKey + ".move").toLowerCase(); +// } + +// function refreshHeader(data: TreeItemData) { +// data.header.innerHTML = ""; + +// var fold = js.Browser.document.createElement("fancy-tree-icon"); +// data.header.append(fold); + +// if (hasChildren(data.item)) { +// fold.classList.add("caret"); +// fold.addEventListener("click", (e) -> { +// toggleItemOpen(data.item); +// saveState(); +// }); +// } + +// function clickHandler(e: js.html.MouseEvent) { +// var lastSelected = currentItem; +// if (!e.ctrlKey) { +// clearSelection(); +// } + +// var flat = flattenTreeItems(); +// var currentIndex = flat.indexOf(currentItem); +// if (e.shiftKey && currentIndex >= 0) { +// var currentIndex = flat.indexOf(currentItem); +// var newIndex = flat.indexOf(data.item); + +// var min = hxd.Math.imin(currentIndex, newIndex); +// var max = hxd.Math.imax(currentIndex, newIndex); + +// for (i in min...max + 1) { +// setSelection(flat[i], true); +// } +// } else { +// setSelection(data.item, !selection.exists(cast data.item)); +// } + +// if (!(e.shiftKey && !e.ctrlKey) || currentItem == null) +// setCurrent(data.item); +// onSelectionChanged(); +// } + +// var iconContent = getIcon(data.item); +// if (iconContent != null) { +// var icon = js.Browser.document.createElement("fancy-tree-icon"); +// icon.append(iconContent); +// data.header.append(icon); +// icon.onclick = clickHandler; +// } + +// var nameElement = js.Browser.document.createElement("fancy-tree-name"); +// var name = getName(data.item) ?? "undefined"; +// data.name = name; +// data.header.title = name; +// nameElement.innerText = name; +// data.header.append(nameElement); + + + +// if (moveFlags.toInt() != 0) { +// data.header.draggable = true; + +// data.header.ondragstart = (e: js.html.DragEvent) -> { +// var draggedPaths = []; +// moveLastDragOver = null; +// if (!selection.get(cast data.item)) { +// clearSelection(); +// setSelection(data.item, true); +// } +// for (item in getSelectedItems()) { +// var data = getDataOrRoot(item); +// draggedPaths.push(data.path); +// } +// e.dataTransfer.setData(getDragDataType(), haxe.Json.stringify(draggedPaths)); +// trace(e.dataTransfer.types); +// e.dataTransfer.effectAllowed = "move"; +// e.dataTransfer.setDragImage(data.header, 0, 0); +// } + +// data.header.ondragover = (e: js.html.DragEvent) -> { +// if (e.dataTransfer.types.contains(getDragDataType())) { +// var target = getDragTarget(data,e); + +// if (canPreformMove(data, target) == null) +// return; + +// if (target == In) { +// if (moveLastDragOver == data.item) { +// moveLastDragTime += 1; +// } +// else { +// moveLastDragOver = data.item; +// moveLastDragTime = 0; +// } + +// if (moveLastDragTime > 25 && !isDataVisuallyOpen(data)) { +// toggleItemOpen(data.item, true, true, false); +// saveState(); +// } +// } + +// setDragStyle(data.header, target); +// e.preventDefault(); +// } +// } + +// data.header.ondragenter = (e: js.html.DragEvent) -> { +// if (e.dataTransfer.types.contains(getDragDataType())) { +// var target = getDragTarget(data,e); +// if (canPreformMove(data, target) == null) +// return; +// setDragStyle(data.header, target); +// e.preventDefault(); +// } +// } + +// data.header.ondragleave = (e: js.html.DragEvent) -> { +// if (e.dataTransfer.types.contains(getDragDataType())) { +// setDragStyle(data.header, None); +// e.preventDefault(); +// } +// } + +// data.header.ondragexit = (e: js.html.DragEvent) -> { +// if (e.dataTransfer.types.contains(getDragDataType())) { +// setDragStyle(data.header, None); +// e.preventDefault(); +// } +// } + +// data.header.ondrop = (e: js.html.DragEvent) -> { +// if (e.dataTransfer.types.contains(getDragDataType())) { +// var target = getDragTarget(data,e); +// var moveOp = canPreformMove(data, target); + +// setDragStyle(data.header, None); +// e.preventDefault(); + +// if (moveOp == null) +// return; + +// onMove(moveOp.toMove, moveOp.newParent, moveOp.newIndex); +// } +// } +// } +// nameElement.onclick = clickHandler; +// } + +// function canPreformMove(data: TreeItemData, target: GetDragTarget) : {toMove: Array, newParent: TreeItem, newIndex: Int} { +// var toMove = getSelectedItems(); + +// var newParent = getDataOrRoot(data.parent); +// var newIndex = 0; +// var currIndex = newParent.children.indexOf(data.item); + +// switch(target) { +// case Top: +// newIndex = currIndex; +// case Bot: +// newIndex = currIndex + 1; +// case In: +// newParent = data; +// default: +// } + +// // Take into account that the item are removed from the children list before getting inserted at the new index, +// // which causes the index to not match if these items are before the target +// if (target == Top || target == Bot) { +// for (item in toMove) { +// var indexInParent = newParent.children.indexOf(item); +// if (indexInParent >= 0 && indexInParent < currIndex) { +// newIndex --; +// } +// } +// } + +// if (!canReparentTo(toMove, newParent.item)) { +// return null; +// } + +// return {toMove: toMove, newParent: newParent.item, newIndex: newIndex}; +// } + + +// function setDragStyle(element: js.html.Element, target: GetDragTarget) { +// trace(target); +// element.classList.toggle("feedback-drop-top", target == Top); +// element.classList.toggle("feedback-drop-bot", target == Bot); +// element.classList.toggle("feedback-drop-in", target == In); +// } + +// function getDragTarget(data: TreeItemData, event: js.html.DragEvent) : GetDragTarget { +// var element = data.element; +// // var canDropIn = moveFlags.has(Reparent) && canReparentTo() +// if (!moveFlags.has(Reorder)) { +// return In; +// } + +// var rect = element.getBoundingClientRect(); +// var name = element.querySelector("fancy-tree-name"); +// var nameRect = element.getBoundingClientRect(); + +// var padding = js.Browser.window.getComputedStyle(element).getPropertyValue; +// if (moveFlags.has(Reparent) && event.clientX > nameRect.left + 100) { +// return In; +// } + +// if (event.clientY > rect.top + rect.height / 2) { +// return Bot; +// } +// return Top; +// } + +// function getElement(item : TreeItem, parent: TreeItem, depth: Int) : js.html.Element { +// var data = getDataOrRoot(item); +// data.depth = depth; +// data.parent = parent; + +// if (data.element == null) { +// data.element = js.Browser.document.createElement("fancy-tree-item"); +// data.element.style.setProperty("--depth", Std.string(depth)); + +// var header = js.Browser.document.createElement("fancy-tree-header"); +// data.header = header; +// data.element.append(header); +// refreshHeader(data); +// } + +// data.path = getDataOrRoot(parent).path + "/" + getUniqueName(item); + +// refreshItemSelection(item, selection.get(cast item) ?? false); + +// return data.element; +// } + +// public function clearSelection() { +// for (item => _ in selection) { +// var item : TreeItem = cast item; +// refreshItemSelection(item, false); +// } +// selection.clear(); +// } + +// public function setSelection(item: TreeItem, newStatus: Bool) { +// if (newStatus == true) +// selection.set(cast item, true); +// else +// selection.remove(cast item); +// refreshItemSelection(item, newStatus); +// } + +// public function setCurrent(item: TreeItem) { +// if (currentItem != null) { +// var data = getDataOrRoot(currentItem); +// data?.element?.classList.remove("current"); +// } +// currentItem = item; +// var data = getDataOrRoot(item); +// data?.element?.classList.add("current"); + +// ensureVisible(data); +// } + +// public function ensureVisible(data: TreeItemData) { +// if (data?.element != null) { +// var root = element.get(0); +// var rootRect = root.getBoundingClientRect(); +// var elemRect = data.element.getBoundingClientRect(); + +// if (elemRect.top < rootRect.top) { +// data.element.scrollIntoView(true); +// } + +// if (elemRect.bottom > rootRect.bottom) { +// data.element.scrollIntoView(false); +// } +// } +// } + +// public function flattenTreeItems() : Array { +// var flat : Array = []; +// function flatten(item: TreeItem) { +// var data = getDataOrRoot(item); +// if (!data.passSearch) +// return; +// if (item != null) +// flat.push(item); + +// if (data.children != null && isDataVisuallyOpen(data)) { +// for (child in data.children) { +// flatten(child); +// } +// } +// } +// flatten(null); +// return flat; +// } + +// public function moveCurrent(delta: Int) { +// if (delta == 0) +// return; + +// var flat = flattenTreeItems(); + +// var currentIndex = flat.indexOf(currentItem); +// if (currentIndex < 0) { +// if (rootData.children == null) +// return; + +// currentItem = flat[0]; +// setCurrent(currentItem); + +// if (searchBar.isOpen() && searchBar.hasFocus()) { +// searchBar.blur(); +// element.focus(); +// } +// return; +// } + +// var nextIndex = currentIndex + delta; +// if (nextIndex < 0) { +// if (searchBar.isOpen()) { +// searchBar.focus(); +// setCurrent(null); +// return; +// } +// else { +// nextIndex = 0; +// } +// } +// else { +// if (searchBar.isOpen() && searchBar.hasFocus()) { +// searchBar.blur(); +// element.focus(); +// } +// } + +// if (nextIndex > flat.length-1) +// nextIndex = flat.length-1; + +// if (nextIndex != currentIndex) { +// setCurrent(flat[nextIndex]); +// } +// } + +// function refreshItemSelection(item: TreeItem, status: Bool) { +// var data = itemMap.get(cast item); +// if (data?.element == null) +// return; +// data.element.classList.toggle("selected", status); +// } +// } \ No newline at end of file diff --git a/hide/comp/Scene.hx b/hide/comp/Scene.hx index 075dc696f..c8f8a9191 100644 --- a/hide/comp/Scene.hx +++ b/hide/comp/Scene.hx @@ -24,6 +24,7 @@ class Scene extends hide.comp.Component implements h3d.IDrawable { public var visible(default, null) : Bool = true; public var editor : hide.comp.SceneEditor; public var autoDisposeOutOfDocument : Bool = true; + public var autoUpdate : Bool = true; var currentRenderProps: hrt.prefab.Reference; @@ -249,6 +250,8 @@ class Scene extends hide.comp.Component implements h3d.IDrawable { } function sync() { + if (!autoUpdate) + return; if ( ide.isDebugger ) doSync(); else { @@ -258,7 +261,7 @@ class Scene extends hide.comp.Component implements h3d.IDrawable { } catch (e:haxe.Exception) { var e = errorHandler(e); if (e != null) { - throw e; + js.Lib.rethrow(); } } if (!errorThisFrame) { @@ -517,10 +520,12 @@ class Scene extends hide.comp.Component implements h3d.IDrawable { var e; if( reload ) @:privateAccess hxd.res.Loader.currentInstance.cache.remove(path); - if( ide.isDebugger ) - e = hxd.res.Loader.currentInstance.load(relPath); - else - e = try hxd.res.Loader.currentInstance.load(relPath) catch( e : hxd.res.NotFound ) null; + e = try { + hxd.res.Loader.currentInstance.load(relPath); + } catch( e : hxd.res.NotFound ) { + ide.quickError('Failed to load HMD at path $path : $e'); + null; + } if( e == null ) { var data = sys.io.File.getBytes(fullPath); if( data.get(0) != 'H'.code ) { @@ -553,7 +558,7 @@ class Scene extends hide.comp.Component implements h3d.IDrawable { return hmd; } - public function resetCamera( ?obj : h3d.scene.Object, distanceFactor = 1. ) { + public function resetCamera( ?obj : h3d.scene.Object, distanceFactor = 1. , ?maxDist: Float) { if( defaultCamera != null ) { s3d.camera.load(defaultCamera); @@ -568,6 +573,9 @@ class Scene extends hide.comp.Component implements h3d.IDrawable { var dy = Math.max(Math.abs(b.yMax),Math.abs(b.yMin)); var dz = Math.max(Math.abs(b.zMax),Math.abs(b.zMin)); var dist = Math.max(Math.max(dx * 6, dy * 6), dz * 4) * distanceFactor; + if (maxDist != null) { + dist = Math.min(maxDist, dist); + } var ang = Math.PI / 4; var zang = Math.PI * 0.4; s3d.camera.pos.set(Math.sin(zang) * Math.cos(ang) * dist, Math.sin(zang) * Math.sin(ang) * dist, Math.cos(zang) * dist); @@ -727,7 +735,7 @@ class PreviewCamController extends h3d.scene.Object { var pushing : Int = -1; var ignoreNext : Bool = false; function onEvent(e : hxd.Event) { - if (getScene().children.length <= 1) + if (getScene()?.children.length <= 1) return; switch (e.kind) { case EPush: { diff --git a/hide/comp/SceneEditor.hx b/hide/comp/SceneEditor.hx index 330eec7a6..273734d40 100644 --- a/hide/comp/SceneEditor.hx +++ b/hide/comp/SceneEditor.hx @@ -1114,6 +1114,23 @@ class SceneEditor { onResize(); }; + sceneEl.get(0).ondragover = (e: js.html.DragEvent) -> { + if (e.dataTransfer.types.contains(hide.view.FileBrowser.dragKey)) { + e.preventDefault(); + return; + } + } + + sceneEl.get(0).ondrop = (e: js.html.DragEvent) -> { + if (e.dataTransfer.types.contains(hide.view.FileBrowser.dragKey)) { + var files : Array = haxe.Json.parse(e.dataTransfer.getData(hide.view.FileBrowser.dragKey)); + @:privateAccess scene.canvas.focus(); + onDragDrop(files, true, e); + e.preventDefault(); + return; + } + } + editorDisplay = true; view.keys.register("copy", {name: "Copy", category: "Edit"}, onCopy); diff --git a/hide/comp/ScenePreview.hx b/hide/comp/ScenePreview.hx index 26b5802cd..bf6e951f6 100644 --- a/hide/comp/ScenePreview.hx +++ b/hide/comp/ScenePreview.hx @@ -20,7 +20,9 @@ class ScenePreview extends Scene { public function new(config, parent, el, save: String) { this.saveDisplayKey = save; super(config, parent, el); + } + public function addToolbar() { var toolbar = new Element('
@@ -112,6 +114,10 @@ class ScenePreview extends Scene { } function listRenderProps() : Array<{name: String, value: String}> { + return listRenderPropsStatic(config); + } + + static public function listRenderPropsStatic(config: hide.Config) : Array<{name: String, value: String}> { var renderProps = config.getLocal("scene.renderProps"); var ret : Array<{name: String, value: String}> = []; diff --git a/hide/tools/FileManager.hx b/hide/tools/FileManager.hx new file mode 100644 index 000000000..4dbf0c4a2 --- /dev/null +++ b/hide/tools/FileManager.hx @@ -0,0 +1,212 @@ +package hide.tools; + + +enum abstract GenToManagerCommand(String) { + var success; +} + +enum abstract ManagerToGenCommand(String) { + var queue; + var prio; + var clear; +} + +typedef GenToManagerSuccessMessage = { + var originalPath : String; + var thumbnailPath : String; +} + +typedef FileData = { + name: String, + parent: FileData, +} + +typedef MiniatureReadyCallback = (miniaturePath: String) -> Void; + +/** + Class that handle parsing and maintaining the state of the project files, and generate miniatures for them on demand +**/ +class FileManager { + + public static final thumbnailGeneratorPort = 9669; + public static final thumbnailGeneratorUrl = "localhost"; + + public static var inst(get, default) : FileManager; + + var generatorWindow : nw.Window; + + var onReadyCallbacks : Map = []; + + var serverSocket : hxd.net.Socket = null; + var generatorSocket : hxd.net.Socket = null; + + static function get_inst() { + if (inst == null) { + inst = new FileManager(); + } + return inst; + } + + public static function onBeforeReload() { + if (inst != null) { + inst.cleanupGenerator(); + } + } + + var reloadQueued = false; + + function queueReload() { + if (reloadQueued == false) { + reloadQueued = true; + haxe.Timer.delay(setupGenerator, 5000); + } + } + + function setupGenerator() { + reloadQueued = false; + serverSocket = new hxd.net.Socket(); + serverSocket.onError = (msg) -> { + hide.Ide.inst.quickError("FileManager socket error : " + msg); + cleanupGenerator(); + queueReload(); + } + serverSocket.bind(thumbnailGeneratorUrl, thumbnailGeneratorPort, (remoteSocket) -> { + if (generatorSocket != null) { + generatorSocket.close(); + } + generatorSocket = remoteSocket; + generatorSocket.onError = (msg) -> { + hide.Ide.inst.quickError("Generator socket error : " + msg); + cleanupGenerator(); + queueReload(); + } + + var handler = new hide.tools.ThumbnailGenerator.MessageHandler(generatorSocket, processThumbnailGeneratorMessage); + + // resend command that weren't completed + for (path => _ in onReadyCallbacks) { + sendGenerateCommand(path); + } + }); + + nw.Window.open('app.html?thumbnail=true', cast { + new_instance: true, + show: false, + title: "HideThumbnailGenerator" + }, (win: nw.Window) -> { + generatorWindow = win; + win.on("close", () -> { + + cleanupGenerator(); + }); + }); + } + + function cleanupGenerator() { + if (generatorSocket != null) { + generatorSocket.close(); + generatorSocket = null; + } + + if (serverSocket != null) { + serverSocket.close(); + serverSocket = null; + } + + if (generatorWindow != null) { + generatorWindow.close(true); + generatorWindow = null; + } + untyped nw.Window.getAll((win:nw.Window) -> { + if (win.title == "HideThumbnailGenerator") { + win.close(true); + } + }); + } + + function new() { + setupGenerator(); + } + + function processThumbnailGeneratorMessage(message: String) { + try { + var message = haxe.Json.parse(message); + switch(message.type) { + case success: + var message : GenToManagerSuccessMessage = message.data; + var cb = onReadyCallbacks.get(message.originalPath); + if (cb == null) { + return; + //throw "Generated a thumbnail for a file not registered"; + } + cb(message.thumbnailPath); + onReadyCallbacks.remove(message.originalPath); + default: + throw "Unknown message type " + message.type; + } + } catch(e) { + hide.Ide.inst.quickError("Thumb Generator invalid message : " + e + "\n" + message); + } + } + + var queued = false; + + /** + Asyncrhonusly generates a miniature. + onReady is called back with the path of the loaded miniature, or null if the miniature couldn't be loaded + **/ + public function renderMiniature(path: String, onReady: MiniatureReadyCallback) { + var ext = path.split(".").pop(); + switch(ext) { + case "prefab" | "fbx" | "l3d" | "fx" | "shgraph" | "jpg" | "jpeg" | "png": + if (!onReadyCallbacks.exists(path)) { + onReadyCallbacks.set(path, onReady); + sendGenerateCommand(path); + } + default: + onReady(null); + } + } + + public function clearRenderQueue() { + onReadyCallbacks.clear(); + if (generatorSocket == null) { + return; + } + var message = { + type: ManagerToGenCommand.clear, + }; + var cmd = haxe.Json.stringify(message) + "\n"; + generatorSocket.out.writeString(cmd); + } + + public function setPriority(path: String, newPriority: Int) { + if (!onReadyCallbacks.exists(path)) { + return; + } + if (generatorSocket == null) { + return; + } + var message = { + type: ManagerToGenCommand.prio, + path: path, + prio: newPriority + }; + var cmd = haxe.Json.stringify(message) + "\n"; + generatorSocket.out.writeString(cmd); + } + + function sendGenerateCommand(path: String) { + if (generatorSocket == null) { + return; + } + var message = { + type: ManagerToGenCommand.queue, + path: path, + }; + var cmd = haxe.Json.stringify(message) + "\n"; + generatorSocket.out.writeString(cmd); + } + + +} \ No newline at end of file diff --git a/hide/tools/ThumbnailGenerator.hx b/hide/tools/ThumbnailGenerator.hx new file mode 100644 index 000000000..e35fc71e6 --- /dev/null +++ b/hide/tools/ThumbnailGenerator.hx @@ -0,0 +1,414 @@ +package hide.tools; + +typedef RenderInfo = {path: String, cb: hide.tools.FileManager.MiniatureReadyCallback, priority: Int}; + +/** + Handle recieving messages separated by `\n` characters by a socket, correctly buffering the data +**/ +class MessageHandler { + var socket: hxd.net.Socket; + var bufferedData : haxe.io.Bytes; + var bufferSize = 0; + static final maxBufferSize = 16384; + + public function new(socket: hxd.net.Socket, callback: (content: String) -> Void) { + this.socket = socket; + bufferedData = haxe.io.Bytes.alloc(maxBufferSize); + bufferSize = 0; + + socket.onData = () -> { + while(socket.input.available > 0) { + var read = hxd.Math.imin(maxBufferSize - bufferSize, socket.input.available); + if (read == 0) { + throw "message too long"; + } + + socket.input.readFullBytes(bufferedData, bufferSize, read); + bufferSize += read; + + var last = 0; + var pos = 0; + + // split on newLines + while(pos < bufferSize) { + if (bufferedData.get(pos) == 10) { + var command = bufferedData.getString(last, pos-last); + callback(command); + last = pos+1; + } + pos ++; + } + + if (last > 0) { + var remaining = bufferSize - last; + if (remaining > 0) { + bufferedData.blit(0, bufferedData, last, remaining); + bufferSize = remaining; + } else { + bufferSize = 0; + } + } else if (bufferSize == maxBufferSize) { + throw "message too long"; + } + } + } + } + +} + +@:access(hide.tools.FileManager) +class ThumbnailGenerator { + var miniaturesToRender : Array = []; + var prioDirty = false; + var renderCanvas : hide.comp.Scene; + var renderTexture : h3d.mat.Texture; + final renderRes = 512; + + var sceneRoot : h3d.scene.Object; + + var socket : hxd.net.Socket = null; + var ready : Bool = false; + + static final debugBypassCache = false; + static final debugShowWindow = false; + + function sendSuccess(originalPath: String, finalPath: String) { + var message = { + type: hide.tools.FileManager.GenToManagerCommand.success, + data: ({ + originalPath: originalPath, + thumbnailPath: finalPath, + }:hide.tools.FileManager.GenToManagerSuccessMessage) + }; + var serialized = haxe.Json.stringify(message); + socket.out.writeString(serialized + "\n"); + } + + var bufferedData : haxe.io.Bytes; + var bufferSize = 0; + static final maxBufferSize = 16384; + + + + function new() { + if (debugShowWindow) { + nw.Window.get().show(true); + } else { + untyped nw.Window.get().hide(); + } + nw.Window.get().resizeTo(128,128); + + bufferedData = haxe.io.Bytes.alloc(maxBufferSize); + + socket = new hxd.net.Socket(); + + // Destroy the generator if any error occurs + socket.onError = (msg) -> { + nw.Window.get().close(true); + } + + var handler = new MessageHandler(socket, handleCommand); + + socket.connect(hide.tools.FileManager.thumbnailGeneratorUrl, hide.tools.FileManager.thumbnailGeneratorPort, () -> { + }); + + var cont = new Element('
').appendTo(js.Browser.document.body); + renderCanvas = new hide.comp.Scene(hide.Ide.inst.currentConfig, cont, null); + renderCanvas.enableNewErrorSystem = true; + renderCanvas.errorHandler = (e) -> { + // do nothing; + return null; + } + renderCanvas.autoUpdate = false; + renderCanvas.onReady = () -> { + renderCanvas.engine.setCurrent(); + + renderCanvas.s3d.removeChildren(); + renderCanvas.s2d.removeChildren(); + + renderTexture = new h3d.mat.Texture(renderRes,renderRes, [Target]); + + sceneRoot = new h3d.scene.Object(renderCanvas.s3d); + + renderCanvas.errorHandler = (e) -> null; + + var renderPropsList = hide.comp.ScenePreview.listRenderPropsStatic(hide.Ide.inst.config.current); + if (renderPropsList.length > 0) { + renderCanvas.setRenderProps(renderPropsList[0].value); + } + + + haxe.Timer.delay(() -> { + this.ready = true; + }, 100); + }; + } + + function handleCommand(command: String) { + var message : Dynamic = {}; + try { + message = haxe.Json.parse(command); + } catch (e) { + return; + } + switch((message.type:FileManager.ManagerToGenCommand)) { + case queue: + var thumbPath = getThumbPath(message.path).toString(); + + var shouldGenerate = true; + if (!debugBypassCache && sys.FileSystem.exists(thumbPath)) { + var thumbStat = sys.FileSystem.stat(thumbPath); + var fileStat = sys.FileSystem.stat(message.path); + if (thumbStat.mtime.getTime() > fileStat.mtime.getTime()) { + shouldGenerate = false; + sendSuccess(message.path, thumbPath); + } + } + + if (shouldGenerate) { + renderMiniature(message.path, sendSuccess.bind(message.path)); + } + case clear: + miniaturesToRender = []; + case prio: + var toSet = Lambda.find(miniaturesToRender, (m) -> m.path == message.path); + if (toSet != null) { + toSet.priority = message.prio; + prioDirty = true; + } + } + } + + var queued = false; + + /** + Asynchronously generates a miniature. + onReady is called back with the path of the loaded miniature, or null if the miniature couldn't be loaded + **/ + public function renderMiniature(path: String, onReady: hide.tools.FileManager.MiniatureReadyCallback) { + miniaturesToRender.push({path: path, cb: onReady, priority: 0}); + if (!queued) { + haxe.Timer.delay(processMiniature, 1); + } + } + + public static final thumbRoot = ".tmp/"; + public static final thumbExt = "thumb.jpg"; + + static public function getThumbPath(basePath: String) : haxe.io.Path { + basePath = StringTools.replace(basePath, hide.Ide.inst.resourceDir, ""); + var path = new haxe.io.Path(haxe.io.Path.join([hide.Ide.inst.resourceDir, thumbRoot, basePath])); + path.ext = thumbExt; + return path; + } + + function handleModel(toRender: RenderInfo) { + renderCanvas.engine.setCurrent(); + + sceneRoot.removeChildren(); + + var engine = renderCanvas.engine; + + var ctx = new hide.prefab.ContextShared(null, sceneRoot); + ctx.scene = renderCanvas; + + var ext = toRender.path.split(".").pop(); + + var abort = false; + if (ext == "fbx") { + var model = new hrt.prefab.Model(null, null); + model.source = toRender.path; + model.make(ctx); + } else if (ext == "prefab" || ext == "l3d" || ext == "fx") { + try { + var ref = new hrt.prefab.Reference(null, null); + var cut = StringTools.replace(toRender.path, hide.Ide.inst.resourceDir + "/", ""); + ref.source = cut; + + var prefab = ref.make(ctx); + + if (ext == "fx") { + var fx = prefab.find(hrt.prefab.fx.FX, true, false); + if (fx != null) { + var fxAnim = Std.downcast(fx.local3d, hrt.prefab.fx.FX.FXAnimation); + // Forward the animations a little bit to show something more usefull + if (fxAnim != null) { + var duration = fxAnim.duration; + fxAnim.setTime(duration * 0.25); + } + } + } + + } catch (e) { + hide.Ide.inst.quickError('miniature render fail for ${toRender.path} : $e'); + abort = true; + } + } else if (ext == "shgraph") { + try { + var spherePrim = new h3d.prim.Sphere(1.0, 32, 32, 1); + spherePrim.addNormals(); + spherePrim.addUVs(); + spherePrim.addTangents(); + + var sphere = new h3d.scene.Mesh(spherePrim, sceneRoot); + + var shgraph = new hrt.prefab.DynamicShader(null, null); + var cut = StringTools.replace(toRender.path, hide.Ide.inst.resourceDir + "/", ""); + shgraph.source = cut; + ctx = new hide.prefab.ContextShared(null, sphere); + shgraph.makeShader(); + for (m in sphere.getMaterials()) { + @:privateAccess shgraph.applyShader(sphere, m, shgraph.shader); + } + } catch(e) { + hide.Ide.inst.quickError('miniature render fail for ${toRender.path} : $e'); + abort = true; + } + } + if (!abort) { + try { + + renderCanvas.resetCamera(sceneRoot, 0.85, 32.0); + + renderTexture.clear(0,0); + + @:privateAccess renderCanvas.doSync(); + + engine.pushTarget(renderTexture); + engine.clear(); + renderCanvas.s3d.render(engine); + engine.popTarget(); + + sceneRoot.removeChildren(); + + var path = convertAndWriteThumbnail(toRender.path, renderTexture); + toRender.cb(path); + } + catch (e) { + hide.Ide.inst.quickError('miniature render fail for ${toRender.path} : $e'); + toRender.cb(null); + } + } else { + toRender.cb(null); + } + } + + function convertAndWriteThumbnail(basePath: String, texture: h3d.mat.Texture) { + var path = getThumbPath(basePath).toString(); + path = StringTools.replace(path, "\\", "/"); + + var dir = path.split("/"); + dir.pop(); + var dirPath = dir.join("/") + "/"; + if(!sys.FileSystem.isDirectory( hide.Ide.inst.getPath(dirPath))) + sys.FileSystem.createDirectory( hide.Ide.inst.getPath(dirPath)); + + var pixels = texture.capturePixels(); + pixels.convert(ARGB); + //sys.io.File.saveBytes(path, renderTexture.capturePixels().toPNG()); + var bytes = new haxe.io.BytesOutput(); + var writer = new format.jpg.Writer(bytes); + writer.write({ + width: texture.width, + height: texture.height, + pixels: pixels.bytes, + quality: 50 + }); + + sys.io.File.saveBytes(path, bytes.getBytes()); + return path; + } + + function handleTexture(toRender: RenderInfo) { + renderCanvas.engine.setCurrent(); + + var cut = StringTools.replace(toRender.path, hide.Ide.inst.resourceDir + "/", ""); + var img = hxd.res.Loader.currentInstance.load(cut).toTexture(); + var width = img.width; + var height = img.height; + + final size = 512; + + if (width > height) { + height = hxd.Math.floor(height / width * size); + width = size; + } else if (width < height) { + width = hxd.Math.floor(width / height * size); + height = size; + } else { + width = size; + height = size; + } + + renderCanvas.s2d.removeChildren(); + + var bg = new h2d.Bitmap(h2d.Tile.fromColor(0), renderCanvas.s2d); + bg.width = size; + bg.height = size; + + var bmp = new h2d.Bitmap(h2d.Tile.fromTexture(img), renderCanvas.s2d); + bmp.width = width; + bmp.height = height; + bmp.x = (size - width) / 2; + bmp.y = (size - height) / 2; + + bmp.blendMode = None; + + var shader = new hide.view.GraphEditor.PreviewShaderAlpha(); + bmp.addShader(shader); + + + var engine = renderCanvas.engine; + + engine.pushTarget(renderTexture); + engine.clear(); + renderCanvas.render(engine); + engine.popTarget(); + + renderCanvas.s2d.removeChildren(); + + var path = convertAndWriteThumbnail(toRender.path, renderTexture); + + // restore renderTexture original size + renderTexture.resize(512, 512); + + toRender.cb(path); + } + + function processMiniature() { + if (!ready || renderCanvas.s3d == null) { + haxe.Timer.delay(processMiniature, 1); + return; + } + + queued = false; + + if (miniaturesToRender.length == 0) { + return; + } + + if (prioDirty) { + miniaturesToRender.sort((a, b) -> Reflect.compare(a.priority, b.priority)); + prioDirty = false; + } + + var toRender = miniaturesToRender.pop(); + + var ext = toRender.path.split(".").pop(); + switch(ext) { + case "prefab" | "fbx" | "l3d" | "fx" | "shgraph": + handleModel(toRender); + case "jpg" | "jpeg" | "png": + handleTexture(toRender); + default: + toRender.cb(null); + } + + + if (miniaturesToRender.length > 0) { + haxe.Timer.delay(processMiniature, 1); + return; + } + + } +} \ No newline at end of file diff --git a/hide/ui/Keys.hx b/hide/ui/Keys.hx index 67fd75b51..ce49c0b6f 100644 --- a/hide/ui/Keys.hx +++ b/hide/ui/Keys.hx @@ -77,6 +77,25 @@ class Keys { return false; } + static public function matchJsEvent(shortcutName : String, event: js.html.KeyboardEvent, config : Config) { + var keyCode : String = config.get("key."+shortcutName); + + var split = keyCode.split("-"); + for (part in split) { + switch (part) { + case "Shift": + if(!event.shiftKey) return false; + case "Ctrl": + if(!event.ctrlKey) return false; + case "Alt": + if(!event.altKey) return false; + default: + if(hxd.Key.getKeyName(event.keyCode) != part) return false; + } + } + return true; + } + public function triggerKey( e : Element.Event, key : String, config : Config ) { for( l in listeners ) if( l(e) ) diff --git a/hide/ui/View.hx b/hide/ui/View.hx index 2d1914d32..55f61333d 100644 --- a/hide/ui/View.hx +++ b/hide/ui/View.hx @@ -1,13 +1,14 @@ package hide.ui; -enum DisplayPosition { - Left; - Center; - Right; - Bottom; +enum abstract DisplayPosition(String) from String to String { + var Left = "content_left"; + var Center = "content_center"; + var Right = "content_right"; + var Bottom = "content_bottom"; + var MiddleColumnInternal = "content_middle_internal"; } -typedef ViewOptions = { ?position : DisplayPosition, ?width : Int } +typedef ViewOptions = { ?position : DisplayPosition, ?width : Int, ?id: String } @:keepSub @:allow(hide.Ide) class View extends hide.comp.Component { diff --git a/hide/view/FileBrowser.hx b/hide/view/FileBrowser.hx new file mode 100644 index 000000000..3efbd99b1 --- /dev/null +++ b/hide/view/FileBrowser.hx @@ -0,0 +1,313 @@ +package hide.view; + +typedef FileBrowserState = { + +} + +enum FileKind { + Dir; + File; +} + +typedef FileEntry = { + name: String, + children: Array, + kind: FileKind, + parent: FileEntry, + iconPath: String, +} + +class FileBrowser extends hide.ui.View { + + var fileTree: Element; + var fileIcons: Element; + + var root : FileEntry; + + override function new(state) { + super(state); + } + + override function onDragDrop(items:Array, isDrop:Bool, event:js.html.DragEvent):Bool { + return false; + } + + function populateChildren(file: FileEntry) { + var fullPath = getFileEntryPath(file); + var paths = js.node.Fs.readdirSync(fullPath); + file.children = []; + for (path in paths) { + if (StringTools.startsWith(path, ".")) + continue; + var info = js.node.Fs.statSync(fullPath + "/" + path); + file.children.push({ + name: path, + kind: info.isDirectory() ? Dir : File, + parent: file, + children: null, + iconPath: null, + }); + } + + file.children.sort(compareFile); + } + + // sort directories before files, and then dirs and files alphabetically + function compareFile(a: FileEntry, b: FileEntry) { + if (a.kind != b.kind) { + if (a.kind == Dir) { + return -1; + } + return 1; + } + return Reflect.compare(a.name, b.name); + } + + function getFileEntryPath(file: FileEntry) { + if (file.parent == null) return ide.resourceDir; + return getFileEntryPath(file.parent) + "/" + file.name; + } + + public static final dragKey = "application/x.filemove"; + + var currentFolder : FileEntry; + var currentSearch = []; + var searchString: String = ""; + var fancyGallery : hide.comp.FancyGallery; + var fancyTree: hide.comp.FancyTree; + + + function onSearch() { + hide.tools.FileManager.inst.clearRenderQueue(); + currentSearch = []; + if (searchString.length == 0) { + currentSearch = currentFolder.children; + } else { + function rec(files: Array) { + for (file in files) { + if (file.kind == Dir) { + rec(file.children); + } + else { + var range = hide.comp.FancySearch.computeSearchRanges(file.name, searchString); + if (range != null) { + currentSearch.push(file); + } + } + } + } + + rec(currentFolder.children); + } + + for (i => _ in currentSearch) { + var child = currentSearch[currentSearch.length - i - 1]; + if ((child.iconPath == null || child.iconPath == "loading") && child.kind == File) { + child.iconPath = "loading"; + hide.tools.FileManager.inst.renderMiniature(getFileEntryPath(child), (path: String) -> {child.iconPath = path; fancyGallery.queueRefresh();} ); + } + } + + fancyGallery.queueRefresh(Items); + fancyGallery.queueRefresh(RegenHeader); + } + + override function onDisplay() { + root = { + name: "res", + kind: Dir, + children: null, + parent: null, + iconPath: null, + }; + + populateChildren(root); + + var layout = new Element(' + +
+
+ + +
+
+ ').appendTo(element); + + var resize = new hide.comp.ResizablePanel(Horizontal, layout.find(".left"), After); + + var search = new hide.comp.FancySearch(null, layout.find(".fb-search")); + search.onSearch = (string, _) -> { + searchString = string; + onSearch(); + }; + + var btnParent = layout.find(".btn-parent"); + btnParent.get(0).onclick = (e: js.html.MouseEvent) -> { + if (currentFolder.parent != null) { + currentFolder = currentFolder.parent; + onSearch(); + } + } + + fancyTree = new hide.comp.FancyTree(resize.element); + fancyTree.saveDisplayKey = "fileBrowserTree"; + fancyTree.getChildren = (file: FileEntry) -> { + if (file == null) + return [root]; + if (file.kind == File) + return null; + if (file.children == null) + populateChildren(file); + return file.children.filter((file) -> file.kind == Dir); + }; + //fancyTree.hasChildren = (file: FileEntry) -> return file.kind == Dir; + fancyTree.getName = (file: FileEntry) -> return file?.name; + fancyTree.getIcon = (file: FileEntry) -> return '
'; + + fancyTree.onNameChange = (item: FileEntry, newName: String) -> { + item.name = newName; + } + + fancyTree.dragAndDropInterface = + { + onDragStart: function(file: FileEntry, dataTransfer: js.html.DataTransfer) : Bool { + var selection = fancyTree.getSelectedItems(); + if (selection.length <= 0) + return false; + var ser = []; + for (item in selection) { + ser.push(getFileEntryPath(file)); + } + dataTransfer.setData(dragKey, haxe.Json.stringify(ser)); + return true; + }, + getItemDropFlags: function(target: FileEntry, dataTransfer: js.html.DataTransfer) : hide.comp.FancyTree.DropFlags { + var containsFiles = false; + if (dataTransfer.types.contains("Files")) { + containsFiles = true; + } + if (dataTransfer.types.contains(dragKey)) { + containsFiles = true; + } + + if (!containsFiles) { + return hide.comp.FancyTree.DropFlags.ofInt(0); + } + + if (target.kind == Dir) { + return (Reorder:hide.comp.FancyTree.DropFlags) | Reparent; + } + return Reorder; + }, + onDrop: function(target: FileEntry, operation: hide.comp.FancyTree.DropOperation, dataTransfer: js.html.DataTransfer) : Bool { + var files : Array = []; + for (file in dataTransfer.files) { + var path : String = untyped file.path; //file.path is an extension from nwjs or node + path = StringTools.replace(path, "\\", "/"); + files.push(path); + } + + var fileMoveData = dataTransfer.getData(dragKey); + if (fileMoveData.length > 0) { + try { + var unser = haxe.Json.parse(fileMoveData); + for (file in (unser:Array)) { + files.push(file); + } + } catch (e) { + trace("Invalid data " + e); + } + } + + return true; + } + } + + fancyTree.rebuildTree(); + + fancyTree.openItem(root); + + currentFolder = root; + + var right = layout.find(".right"); + right.get(0).onkeydown = (e: js.html.KeyboardEvent) -> { + if (hide.ui.Keys.matchJsEvent("search", e, ide.currentConfig)) { + e.stopPropagation(); + e.preventDefault(); + + search.focus(); + return; + } + } + + fancyGallery = new hide.comp.FancyGallery(null, layout.find(".right fancy-gallery")); + fancyGallery.getItems = () -> { + return currentSearch; + } + + fancyGallery.getName = (item : FileEntry) -> item.name; + + fancyGallery.getIcon = (item : FileEntry) -> { + if (item.kind == Dir) { + return ''; + + } + else if (item.iconPath == "loading") { + return ''; + } + else if (item.iconPath != null) { + var url = "file://" + item.iconPath; + return ''; + } + else { + return ''; + } + }; + + fancyGallery.onDoubleClick = (item: FileEntry) -> { + if (item.kind == File) { + ide.openFile(getFileEntryPath(item)); + } else { + openDir(item, true); + } + } + + fancyGallery.visibilityChanged = (item: FileEntry, visible: Bool) -> { + var path = getFileEntryPath(item); + hide.tools.FileManager.inst.setPriority(path, visible ? 1 : 0); + } + + fancyGallery.dragAndDropInterface = { + onDragStart: (item: FileEntry, dataTransfer: js.html.DataTransfer) -> { + dataTransfer.setData(dragKey, haxe.Json.stringify([getFileEntryPath(item)])); + return true; + } + } + + fancyGallery.rebuild(); + + + fancyTree.onSelectionChanged = () -> { + var selection = fancyTree.getSelectedItems(); + + if (selection.length > 0) { + openDir(selection[0], false); + } + } + + onSearch(); + } + + function openDir(item: FileEntry, syncTree: Bool) { + if (item.kind == Dir) { + currentFolder = item; + onSearch(); + } + + if (syncTree) { + fancyTree.selectItem(item, true); + } + } + + static var _ = hide.ui.View.register(FileBrowser, { width : 350, position : Bottom }); +} \ No newline at end of file diff --git a/hide/view/GenericGraphEditor.hx b/hide/view/GenericGraphEditor.hx index cfd478e35..6200aeae6 100644 --- a/hide/view/GenericGraphEditor.hx +++ b/hide/view/GenericGraphEditor.hx @@ -54,6 +54,7 @@ class GenericGraphEditor extends hide.view.FileView implements IGraphEditor { // Scene init scenePreview = new hide.comp.ScenePreview(config, previewContainer, null, saveDisplayKey + "/scenePreview"); + scenePreview.addToolbar(); scenePreview.element.addClass("scene-preview"); scenePreview.onReady = onScenePreviewReady; diff --git a/hide/view/Gym.hx b/hide/view/Gym.hx index f55b63eb6..b444a7496 100644 --- a/hide/view/Gym.hx +++ b/hide/view/Gym.hx @@ -141,12 +141,189 @@ class Gym extends hide.ui.View<{}> { ')); } + + { + var toolbar = section(element, "Windows"); + + var btn = new Element("Open subwindow 'test'"); + + toolbar.append(btn); + + var subwindow : js.html.Window; + var scene : hide.comp.Scene; + + btn.on("click", (_) -> { + subwindow = js.Browser.window.open("", "test","popup=true"); + + var jq = new Element(subwindow.document.body); + jq.empty(); + jq.append(new Element("

This is a triumph

")); + var container = new Element("
"); + jq.append(container); + + // var paragraphs = subwindow.document.querySelectorAll("p"); + // for (p in paragraphs) { + // p.textContent = "This is the begining of something great"; + // } + + scene = new hide.comp.Scene(config, container, null); + + scene.onReady = () -> { + new h3d.scene.CameraController(scene.s3d); + var box = new h3d.scene.Box(scene.s3d); + box.material.mainPass.setPassName("overlay"); + + var text = new h2d.Text(hxd.res.DefaultFont.get(), scene.s2d); + text.text = "Hello world"; + text.x = 8; + text.y = 8; + }; + + var drag = new Element('
Drag Me
').appendTo(jq); + drag.get(0).addEventListener("dragstart", (ev: js.html.DragEvent) -> { + ev.dataTransfer.setData("text/plain", "foo"); + ev.dataTransfer.dropEffect = "copy"; + }); + }); + + var btn = new Element("Spawn cube in subwindow"); + toolbar.append(btn); + + btn.on("click", (_) -> { + var box = new h3d.scene.Box(0xFFFFFFFF, scene.s3d); + box.setPosition(hxd.Math.random(10),hxd.Math.random(10),hxd.Math.random(10)); + box.material.mainPass.setPassName("overlay"); + box.material.color.r = hxd.Math.random(); + box.material.color.g = hxd.Math.random(); + box.material.color.b = hxd.Math.random(); + }); + + var dropZone = new Element("
Drop something on me from the other window
").appendTo(toolbar); + dropZone.get(0).addEventListener("drop", (ev : js.html.DragEvent) -> { + ev.preventDefault(); + var data = ev.dataTransfer.getData("text/plain"); + dropZone.text(data); + }); + + dropZone.get(0).addEventListener("dragover", (ev : js.html.DragEvent) -> { + ev.preventDefault(); + ev.dataTransfer.dropEffect = "copy"; + }); + + var btn2 = new Element("Localhost 5500").appendTo(toolbar); + btn2.on("click", (_) -> { + subwindow = js.Browser.window.open("http://127.0.0.1:5500/", "test","popup=true"); + }); + } + + + { + var toolbar = section(element, "Offscreen Rendering"); + var btn = new Element("Render test.thumb.png").appendTo(toolbar); + + btn.get(0).onclick = (e) -> { + var fm = hide.tools.FileManager.inst; + } + + var btn = new Element("Test thumbnail generator").appendTo(toolbar); + + var sub : js.node.child_process.ChildProcess = null; + + var remoteSocket : hxd.net.Socket = null; + + btn.get(0).onclick = (e) -> { + + if (sock != null) { + sock.close(); + } + + sock = new hxd.net.Socket(); + + sock.onError = (msg) -> { + trace("Socket error " + msg); + } + + sock.onData = () -> { + trace("sock.onData"); + while(sock.input.available > 0) { + var data = sock.input.readLine().toString(); + + trace("recieved data sock.onData", data); + } + } + + sock.bind("localhost", 9669, (rs: hxd.net.Socket) -> { + trace("new connexion"); + remoteSocket = rs; + + remoteSocket.onError = (msg) -> { + trace("Socket error " + msg); + } + + remoteSocket.onData = () -> { + trace("rawsocket.onData"); + + while(remoteSocket.input.available > 0) { + var data = remoteSocket.input.readLine().toString(); + + trace("recieved data", data); + } + } + }); + + nw.Window.open('app.html?thumbnail=true', {new_instance: true}, (win: nw.Window) -> { + win.on("close", () -> { + sock.close(); + sock = null; + }); + }); + } + + var btn = new Element("Send message").appendTo(toolbar); + btn.get(0).onclick = (e) -> { + remoteSocket.out.writeString("Test message\n"); + } + + var btn = new Element("Rethrow test").appendTo(toolbar); + btn.get(0).onclick = (e) -> { + rethrowTest1(); + } + } + } + + function rethrowTest1() { + try { + rethrowTest2(); + } catch (e) { + js.Lib.rethrow(); + } } + function rethrowTest2() { + try { + rethrowTest3(); + } catch(e) { + js.Lib.rethrow(); + } + } + + function rethrowTest3() { + throw "Error Lol"; + } + + var subwin: js.html.Window; + static var sock: hxd.net.Socket; + + static function section(parent: Element, name: String) : Element { return new Element('
$name
').appendTo(parent); } + static public function onBeforeReload() { + sock?.close(); + sock = null; + } + static function getContextMenuContent() : Array { var radioState = 0; diff --git a/hide/view/Inspector.hx b/hide/view/Inspector.hx new file mode 100644 index 000000000..60ca897b5 --- /dev/null +++ b/hide/view/Inspector.hx @@ -0,0 +1,17 @@ +package hide.view; + +typedef InspectorState = { + +} + +class Inspector extends hide.ui.View { + + override function new(state) { + super(state); + } + override function onDisplay() { + element.html("

Hello world

"); + } + + static var _ = hide.ui.View.register(Inspector, { width : 350, position : Right, id: "inspector" }); +} \ No newline at end of file diff --git a/hide/view/Prefab.hx b/hide/view/Prefab.hx index afd3b72f5..ad3b50a79 100644 --- a/hide/view/Prefab.hx +++ b/hide/view/Prefab.hx @@ -242,6 +242,13 @@ class Prefab extends hide.view.FileView { sceneReadyDelayed.empty(); } + override function onHide() { + super.onHide(); + + //ide.closeInspector(); + + } + override function onDisplay() { if( sceneEditor != null ) sceneEditor.dispose(); createData(); @@ -391,6 +398,8 @@ class Prefab extends hide.view.FileView { tools.refreshToggles(); setRenderPropsEditionVisibility(Ide.inst.currentConfig.get("sceneeditor.renderprops.edit", false)); + + //ide.getOrOpenInspector(); } public function hideColumns(?_) { diff --git a/hide/view/RemoteConsoleView.hx b/hide/view/RemoteConsoleView.hx index 710915484..46be0d4e8 100644 --- a/hide/view/RemoteConsoleView.hx +++ b/hide/view/RemoteConsoleView.hx @@ -180,6 +180,8 @@ class RemoteConsoleView extends hide.ui.View<{}> { haxe.Timer.delay(wait, 10); return; } + if (Ide.inst.thumbnailMode) + return; var config = Ide.inst.config.project; var pconfig = config.get("remoteconsole"); if( pconfig != null && pconfig.disableAutoStartServer != true ) { diff --git a/hide/view/animgraph/BlendSpace2DEditor.hx b/hide/view/animgraph/BlendSpace2DEditor.hx index 1a2a15992..3d133fe38 100644 --- a/hide/view/animgraph/BlendSpace2DEditor.hx +++ b/hide/view/animgraph/BlendSpace2DEditor.hx @@ -300,6 +300,7 @@ class BlendSpace2DEditor extends hide.view.FileView { panel.onResize = refreshGraph; scenePreview = new hide.comp.ScenePreview(config, previewContainer, null, saveDisplayKey + "/preview"); + scenePreview.addToolbar(); scenePreview.listLoadableMeshes = () -> { var ret : Array<{label: String, path: String}> = []; var list = AnimGraphEditor.gatherAllPreviewModels(blendSpace2D.animFolder); diff --git a/hrt/prefab/Model.hx b/hrt/prefab/Model.hx index 7bcfbcd1a..f4a527159 100644 --- a/hrt/prefab/Model.hx +++ b/hrt/prefab/Model.hx @@ -55,8 +55,7 @@ class Model extends Object3D { return obj; #if editor } catch( e : Dynamic ) { - e.message = "Could not load model " + source + ": " + e.message; - shared.onError(e); + hide.Ide.inst.quickError("Could not load model " + source + ": " + e.message); } #end return new h3d.scene.Object(parent3d); diff --git a/hrt/prefab/fx/FX.hx b/hrt/prefab/fx/FX.hx index 0cb63dcca..c879f814e 100644 --- a/hrt/prefab/fx/FX.hx +++ b/hrt/prefab/fx/FX.hx @@ -301,7 +301,7 @@ class FXAnimation extends h3d.scene.Object { var visible = anim.elt.visible; #if editor var editor = anim.elt.shared.editor; - visible = visible && editor.isVisible(anim.elt); + visible = visible && (editor?.isVisible(anim.elt) ?? true); #end anim.obj.visible = visible && evaluator.getFloat(anim.visibility, time) > 0.5; } diff --git a/hrt/prefab/l3d/Trails.hx b/hrt/prefab/l3d/Trails.hx index dc85f436b..58b726ecb 100644 --- a/hrt/prefab/l3d/Trails.hx +++ b/hrt/prefab/l3d/Trails.hx @@ -233,8 +233,11 @@ class TrailObj extends h3d.scene.Mesh { fxAnim.push(fx); p = p.parent; } - for ( fx in fxAnim ) - fx.trails.remove(this); + for ( fx in fxAnim ) { + if (fx.trails != null) { + fx.trails.remove(this); + } + } dprim.dispose(); } diff --git a/libs/golden/ContentItem.hx b/libs/golden/ContentItem.hx index 2bb01dbb8..c286980ee 100644 --- a/libs/golden/ContentItem.hx +++ b/libs/golden/ContentItem.hx @@ -9,6 +9,7 @@ extern class ContentItem { var childElementContainer : Container; var config : Config.ItemConfig; var header : Header; + var id : String; var __view : Dynamic; @@ -19,5 +20,6 @@ extern class ContentItem { public function getItemsByFilter( f : ContentItem -> Bool ) : Array; public function getActiveContentItem() : ContentItem; public function setActiveContentItem( item : ContentItem ) : Void; - + public function getItemsById(id : String) : Array; + public function remove() : Void ; } \ No newline at end of file