import { dispose , Id64String , IDisposable } from "@itwin/core-bentley" ; import { ColorInputProps , ComboBox , ComboBoxHandler , convertHexToRgb , createButton , createCheckBox , createColorInput , createComboBox , createNumericInput , } from "@itwin/frontend-devtools" ; import { FeatureAppearance , FeatureAppearanceProps , LinePixels , RgbColor } from "@itwin/core-common" ; import { FeatureOverrideProvider , FeatureSymbology , Viewport } from "@itwin/core-frontend" ; import { ToolBarDropDown } from "./ToolBar" ; export class Provider implements FeatureOverrideProvider { private readonly _elementOvrs = new Map < Id64String , FeatureAppearance > ( ) ; private _defaultOvrs : FeatureAppearance | undefined ; private readonly _vp : Viewport ; private constructor ( vp ) { this . _vp = vp ; } public addFeatureOverrides ( ovrs , _vp ) { this . _elementOvrs . forEach ( ( appearance , elementId ) => ovrs . override ( { elementId , appearance } ) ) ; if ( undefined !== this . _defaultOvrs ) ovrs . setDefaultOverrides ( this . _defaultOvrs ) ; } public overrideElements ( app ) { for ( const id of this . _vp . iModel . selectionSet . elements ) this . _elementOvrs . set ( id , app ) ; this . sync ( ) ; } public overrideElementsByArray ( elementOvrs ) { elementOvrs . forEach ( ( eo ) => { const fsa = FeatureAppearance . fromJSON ( JSON . parse ( eo . fsa ) as FeatureAppearanceProps ) ; if ( eo . id === "-default-" ) this . defaults = fsa ; else this . _elementOvrs . set ( eo . id , fsa ) ; } ) ; this . sync ( ) ; } public toJSON ( ) { if ( 0 === this . _elementOvrs . size && undefined === this . _defaultOvrs ) return undefined ; const elementOvrs = [ ] ; this . _elementOvrs . forEach ( ( value , key ) => { const elem = { id : key , fsa : JSON . stringify ( value . toJSON ( ) ) } ; elementOvrs . push ( elem ) ; } ) ; if ( undefined !== this . _defaultOvrs ) { const elem = { id : "-default-" , fsa : JSON . stringify ( this . _defaultOvrs . toJSON ( ) ) } ; elementOvrs . push ( elem ) ; } return elementOvrs ; } public clear ( ) { this . _elementOvrs . clear ( ) ; this . _defaultOvrs = undefined ; this . sync ( ) ; } public set defaults ( value ) { this . _defaultOvrs = value ; this . sync ( ) ; } private sync ( ) { this . _vp . setFeatureOverrideProviderChanged ( ) ; } public static get ( vp ) { return vp . findFeatureOverrideProvider ( ( x ) => x instanceof Provider ) as Provider | undefined ; } public static remove ( vp ) { const provider = this . get ( vp ) ; if ( provider ) vp . dropFeatureOverrideProvider ( provider ) ; } public static getOrCreate ( vp ) { let provider = this . get ( vp ) ; if ( undefined === provider ) { provider = new Provider ( vp ) ; vp . addFeatureOverrideProvider ( provider ) ; } return provider ; } } export class Settings implements IDisposable { private _appearance = FeatureAppearance . defaults ; private readonly _vp : Viewport ; private readonly _parent : HTMLElement ; private readonly _element : HTMLElement ; public constructor ( vp , parent ) { this . _vp = vp ; this . _parent = parent ; this . _element = document . createElement ( "div" ) ; this . _element . className = "toolMenu" ; this . _element . style . cssFloat = "left" ; this . _element . style . display = "block" ; this . addColor ( this . _element ) ; this . addTransparency ( this . _element ) ; this . addWeight ( this . _element ) ; Settings . addStyle ( this . _element , LinePixels . Invalid , ( select ) => this . updateStyle ( parseInt ( select . value , 10 ) ) ) ; createCheckBox ( { parent : this . _element , name : "Ignore Material" , id : "ovr_ignoreMaterial" , handler : ( cb ) => this . updateIgnoreMaterial ( cb . checked ? true : undefined ) , } ) ; createCheckBox ( { parent : this . _element , name : "Non-locatable" , id : "ovr_nonLocatable" , handler : ( cb ) => this . updateNonLocatable ( cb . checked ? true : undefined ) , } ) ; createCheckBox ( { parent : this . _element , name : "Emphasized" , id : "ovr_emphasized" , handler : ( cb ) => this . updateAppearance ( "emphasized" , cb . checked ? true : undefined ) , } ) ; createCheckBox ( { parent : this . _element , name : "View-dependent transparency" , id : "ovr_viewDep" , handler : ( cb ) => this . updateAppearance ( "viewDependentTransparency" , cb . checked ? true : undefined ) , } ) ; const buttonDiv = document . createElement ( "div" ) ; buttonDiv . style . textAlign = "center" ; createButton ( { value : "Apply" , handler : ( ) => this . _provider . overrideElements ( this . _appearance ) , parent : buttonDiv , inline : true , tooltip : "Apply overrides to selection set" , } ) ; createButton ( { value : "Default" , handler : ( ) => this . _provider . defaults = this . _appearance , parent : buttonDiv , inline : true , tooltip : "Set as default overrides" , } ) ; createButton ( { value : "Clear" , handler : ( ) => this . _provider . clear ( ) , parent : buttonDiv , inline : true , tooltip : "Remove all overrides" , } ) ; this . _element . appendChild ( document . createElement ( "hr" ) ) ; this . _element . appendChild ( buttonDiv ) ; parent . appendChild ( this . _element ) ; } public dispose ( ) { this . _parent . removeChild ( this . _element ) ; } private get _provider ( ) { return Provider . getOrCreate ( this . _vp ) ; } private updateAppearance ( field , value ) { const props = this . _appearance . toJSON ( ) ; props [ field ] = value ; this . _appearance = FeatureAppearance . fromJSON ( props ) ; } private updateColor ( rgb ) { this . updateAppearance ( "rgb" , rgb ) ; } private updateTransparency ( transparency ) { this . updateAppearance ( "transparency" , transparency ) ; } private updateWeight ( weight ) { this . updateAppearance ( "weight" , weight ) ; } private updateIgnoreMaterial ( ignoresMaterial ) { this . updateAppearance ( "ignoresMaterial" , ignoresMaterial ) ; } private updateNonLocatable ( nonLocatable ) { this . updateAppearance ( "nonLocatable" , nonLocatable ) ; } private updateStyle ( style ) { const linePixels = LinePixels . Invalid !== style ? style : undefined ; this . updateAppearance ( "linePixels" , linePixels ) ; } private addTransparency ( parent ) { const div = document . createElement ( "div" ) ; const cb = document . createElement ( "input" ) ; cb . type = "checkbox" ; cb . id = "cb_ovrTrans" ; div . appendChild ( cb ) ; const label = document . createElement ( "label" ) ; label . htmlFor = "cb_ovrTrans" ; label . innerText = "Transparency " ; div . appendChild ( label ) ; const num = createNumericInput ( { parent : div , value : 0 , disabled : true , min : 0 , max : 255 , step : 1 , handler : ( value ) => this . updateTransparency ( value / 255 ) , } ) ; div . appendChild ( num ) ; cb . addEventListener ( "click" , ( ) => { num . disabled = ! cb . checked ; this . updateTransparency ( cb . checked ? parseInt ( num . value , 10 ) / 255 : undefined ) ; } ) ; parent . appendChild ( div ) ; } private addWeight ( parent ) { const div = document . createElement ( "div" ) ; const cb = document . createElement ( "input" ) ; cb . type = "checkbox" ; cb . id = "cb_ovrWeight" ; div . appendChild ( cb ) ; const label = document . createElement ( "label" ) ; label . htmlFor = "cb_ovrWeight" ; label . innerText = "Weight " ; div . appendChild ( label ) ; const num = createNumericInput ( { parent : div , value : 1 , disabled : true , min : 1 , max : 31 , step : 1 , handler : ( value ) => this . updateWeight ( value ) , } ) ; div . appendChild ( num ) ; cb . addEventListener ( "click" , ( ) => { num . disabled = ! cb . checked ; this . updateWeight ( cb . checked ? parseInt ( num . value , 10 ) : undefined ) ; } ) ; parent . appendChild ( div ) ; } public static addStyle ( parent , value , handler ) { const entries = [ { name : "Not overridden" , value : LinePixels . Invalid } , { name : "Solid" , value : LinePixels . Solid } , { name : "Hidden Line" , value : LinePixels . HiddenLine } , { name : "Invisible" , value : LinePixels . Invisible } , { name : "Code1" , value : LinePixels . Code1 } , { name : "Code2" , value : LinePixels . Code2 } , { name : "Code3" , value : LinePixels . Code3 } , { name : "Code4" , value : LinePixels . Code4 } , { name : "Code5" , value : LinePixels . Code5 } , { name : "Code6" , value : LinePixels . Code6 } , { name : "Code7" , value : LinePixels . Code7 } , ] ; return createComboBox ( { parent , entries , id : "ovr_Style" , name : "Style " , value , handler , } ) ; } private addColor ( parent ) { const div = document . createElement ( "div" ) ; const cb = document . createElement ( "input" ) ; cb . type = "checkbox" ; cb . id = "cb_ovrColor" ; div . appendChild ( cb ) ; const update = ( ) => this . updateColor ( convertHexToRgb ( input . value ) ) ; const props = { parent : div , id : "color_ovrColor" , label : "Color" , value : "#ffffff" , display : "inline" , disabled : true , handler : update , } ; const input = createColorInput ( props ) . input ; cb . addEventListener ( "click" , ( ) => { input . disabled = ! cb . checked ; if ( cb . checked ) update ( ) ; else this . updateColor ( undefined ) ; } ) ; parent . appendChild ( div ) ; } } export class FeatureOverridesPanel extends ToolBarDropDown { private readonly _vp : Viewport ; private readonly _parent : HTMLElement ; private _settings ? : Settings ; public constructor ( vp , parent ) { super ( ) ; this . _vp = vp ; this . _parent = parent ; this . open ( ) ; } public override get onViewChanged ( ) : Promise < void > { Provider . remove ( this . _vp ) ; return Promise . resolve ( ) ; } protected _open ( ) { this . _settings = new Settings ( this . _vp , this . _parent ) ; } protected _close ( ) { this . _settings = dispose ( this . _settings ) ; } public get isOpen ( ) : boolean { return undefined !== this . _settings ; } }