mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 22:51:17 +07:00
Create IDE LaF bridge (#102)
* [WIP] Start building the bridge * Update dependencies, remove unused ones * [WIP] Continue building the bridge * [WIP] Continue building the bridge (got to Chip) Includes tweaks on wrong values etc * [WIP] Fill out infra for IntUiBridge.kt Still not done! * [WIP] Fill out infra for IntUiBridge.kt Still not done! * [WIP] More work * [WIP] More progress (missing text fields & tabs) * Finish filling in the bridge, then 🤞 * Make default fallbacks explicit, fix some issues * Delete unused code * Implement New UI icon mapping support in Bridge Note: it doesn't work yet, since IconMapLoader.loadIconMapping() complains that the mapping isn't done (when it is, since it's done in the IDE pre-loading). I suspect this is a classloading issue. * More cleanup, stuff's still broken yey * More fixin' stuff, hopefully the CI too * Fix CI warning * Refactor UI Component Styling in Jewel to Support defaults (#104) * Refactor UI Component Styling in Jewel to Support defaults Retrieving text styles requires coroutines. This commit aims to allow the theme to start with text defaults and as soon as text styles get retrieved, the theme gets updated. * Fixes according to MR comments * Fix this thing --------- Co-authored-by: Sebastiano Poggi <sebp@google.com> * More fixes to components/theme, cleanups * Improve showcase and simplify text area (bye hints) * Improve showcase and simplify text area (bye hints), fix more things * Avoid crashing on old UI --------- Co-authored-by: Lamberto Basti <basti.lamberto@gmail.com> GitOrigin-RevId: b1d9fa9ec52c64833cd14a453c524195eb8932bd
This commit is contained in:
committed by
intellij-monorepo-bot
parent
274830138a
commit
f3dd498338
4
platform/jewel/.github/workflows/build.yml
vendored
4
platform/jewel/.github/workflows/build.yml
vendored
@@ -39,13 +39,15 @@ jobs:
|
|||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Run :check task
|
- name: Run :check task
|
||||||
run: ./gradlew check
|
run: ./gradlew check --continue
|
||||||
|
|
||||||
- name: Merge SARIF reports
|
- name: Merge SARIF reports
|
||||||
# Necessary because upload-sarif only takes up to 15 SARIF files and we have more
|
# Necessary because upload-sarif only takes up to 15 SARIF files and we have more
|
||||||
run: ./gradlew :mergeSarifReports
|
run: ./gradlew :mergeSarifReports
|
||||||
|
if: ${{ always() }}
|
||||||
|
|
||||||
- uses: github/codeql-action/upload-sarif@v2
|
- uses: github/codeql-action/upload-sarif@v2
|
||||||
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{ github.workspace }}/build/reports/static-analysis.sarif
|
sarif_file: ${{ github.workspace }}/build/reports/static-analysis.sarif
|
||||||
checkout_path: ${{ github.workspace }}
|
checkout_path: ${{ github.workspace }}
|
||||||
|
|||||||
3
platform/jewel/.idea/codeStyles/Project.xml
generated
3
platform/jewel/.idea/codeStyles/Project.xml
generated
@@ -19,6 +19,9 @@
|
|||||||
<option name="IF_RPAREN_ON_NEW_LINE" value="false" />
|
<option name="IF_RPAREN_ON_NEW_LINE" value="false" />
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
</JetCodeStyleSettings>
|
</JetCodeStyleSettings>
|
||||||
|
<editorconfig>
|
||||||
|
<option name="ENABLED" value="false" />
|
||||||
|
</editorconfig>
|
||||||
<codeStyleSettings language="JSON">
|
<codeStyleSettings language="JSON">
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="TAB_SIZE" value="2" />
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
|||||||
1085
platform/jewel/.idea/inspectionProfiles/Lint_only.xml
generated
Normal file
1085
platform/jewel/.idea/inspectionProfiles/Lint_only.xml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,315 +1,19 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="ActionIsNotPreviewFriendly" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintAssertionSideEffect" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintBadHostnameVerifier" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintFileEndsWithExt" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintGradleDeprecatedConfiguration" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintGradlePath" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintJcenterRepositoryObsolete" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintLocalSuppress" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintMissingSuperCall" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintNotConstructor" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintNotInterpolated" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintRange" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintSQLiteString" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintSuspiciousIndentation" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintUseValueOf" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AndroidLintUsingHttp" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AntMissingPropertiesFileInspection" enabled="false" level="ERROR" enabled_by_default="false" />
|
<inspection_tool class="AntMissingPropertiesFileInspection" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||||
<inspection_tool class="ArrayEquals" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ArrayInDataClass" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AssertBetweenInconvertibleTypes" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AssertWithSideEffects" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AssertionCanBeIf" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AssignmentToCatchBlockParameter" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="AtomicFieldUpdaterIssues" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="BooleanConstructor" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="BooleanLiteralArgument" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="BooleanMethodIsAlwaysInverted" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="BoxingBoxedValue" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="CanBeFinal" enabled="false" level="WARNING" enabled_by_default="false">
|
|
||||||
<option name="REPORT_CLASSES" value="false" />
|
|
||||||
<option name="REPORT_METHODS" value="false" />
|
|
||||||
<option name="REPORT_FIELDS" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="CanBeParameter" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="CanBePrimaryConstructorProperty" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="CascadeIf" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="CastCanBeReplacedWithVariable" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="CheckDtdRefs" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="CloneableImplementsClone" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="CollectionContainsUrl" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ComparatorMethodParameterNotUsed" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ComparatorResultComparison" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ComponentNotRegistered" enabled="false" level="WARNING" enabled_by_default="false">
|
|
||||||
<option name="CHECK_ACTIONS" value="true" />
|
|
||||||
<option name="IGNORE_NON_PUBLIC" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="ComponentRegistrationProblems" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ConditionCoveredByFurtherCondition" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ConstantConditionIf" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ConstantConditionalExpression" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ControlFlowStatementWithoutBraces" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ControlFlowWithEmptyBody" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="Convert2MethodRef" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ConvertLambdaToReference" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ConvertReferenceToLambda" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ConvertTwoComparisonsToRangeCheck" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="CopyWithoutNamedArguments" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="DataClassPrivateConstructor" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="Dependency" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="DeprecatedClassUsageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="DeprecatedMavenDependency" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="DeprecatedMavenDependency" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="DoubleBraceInitialization" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="DuplicateExpressions" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EditorConfigCharClassRedundancy" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EditorConfigEncoding" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EditorConfigListAcceptability" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EditorConfigNoMatchingFiles" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EditorConfigOptionRedundancy" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EditorConfigShadowedOption" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EditorConfigShadowingOption" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EditorConfigValueUniqueness" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EditorConfigWildcardRedundancy" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EmptyFinallyBlock" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EmptyRange" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EmptyStatementBody" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EndlessStream" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EnhancedSwitchBackwardMigration" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EnhancedSwitchMigration" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EnumSwitchStatementWhichMissesCases" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EqualsBetweenInconvertibleTypes" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EqualsHashCodeCalledOnUrl" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="EqualsWithItself" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ExcessiveRangeCheck" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ExplicitArgumentCanBeLambda" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ExpressionMayBeFactorized" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ExtendsAnnotation" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="FakeJvmFieldConstant" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="FinalStaticMethod" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="FinalizeNotProtected" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="FinallyBlockCannotCompleteNormally" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="FoldExpressionIntoStream" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="FoldInitializerAndIfToElvis" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ForeignDelegate" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="FuseStreamOperations" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GrMethodMayBeStatic" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GrReassignedInClosureLocalVar" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GrSwitchExhaustivenessCheck" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GrUnnecessaryPublicModifier" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GradlePackageUpdate" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
<inspection_tool class="GradlePackageUpdate" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="GrazieInspection" enabled="false" level="GRAMMAR_ERROR" enabled_by_default="false" />
|
<inspection_tool class="GrazieInspection" enabled="false" level="GRAMMAR_ERROR" enabled_by_default="false" />
|
||||||
<inspection_tool class="GroovyAssignmentCanBeOperatorAssignment" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyConditionalCanBeElvis" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyConditionalWithIdenticalBranches" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyConstantConditional" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyConstructorNamedArguments" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyDoubleNegation" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyDuplicateSwitchBranch" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyEmptyStatementBody" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyInfiniteLoopStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyLabeledStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyResultOfObjectAllocationIgnored" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovySynchronizationOnNonFinalField" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyTrivialIf" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyUnusedAssignment" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="GroovyVariableNotAssigned" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="HardwiredNamespacePrefix" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="HardwiredNamespacePrefix" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="HtmlUnknownBooleanAttribute" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="IOStreamConstructor" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="IgnoreRelativeEntry" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="IncorrectParentDisposable" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="IndexZeroUsage" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="IndexZeroUsage" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="InfiniteRecursion" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="InjectionNotApplicable" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="JUnit5AssertionsConverter" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="Java8MapApi" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="Java9ModuleExportsPackageToItself" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="JavaCollectionsStaticMethodOnImmutableList" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="JavaFxEventHandler" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="JavaFxEventHandler" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="JavaLangInvokeHandleSignature" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="JavaStylePropertiesInvocation" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="JavacQuirks" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="JoinDeclarationAndAssignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="JsonPathEvaluateUnknownKey" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="JsonSchemaRefReference" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="Junit4Converter" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="KDocUnresolvedReference" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="KotlinDeprecation" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="KotlinInternalInJava" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="KotlinJvmAnnotationInJava" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="KotlinPlaceholderCountMatchesArgumentCount" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="KotlinThrowableNotThrown" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="LambdaCanBeMethodCall" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="LambdaCanBeReplacedWithAnonymous" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="LeakableMapKey" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="LeakingThis" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="LiftReturnOrAssignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="LocalVariableName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="Lombok" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MainFunctionReturnUnit" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MalformedFormatString" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ManualMinMaxCalculation" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MarkdownIncorrectTableFormatting" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MarkdownIncorrectlyNumberedListItem" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MarkdownLinkDestinationWithSpaces" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MarkdownNoTableBorders" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MeaninglessRecordAnnotationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MethodRefCanBeReplacedWithLambda" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MigrateDiagnosticSuppression" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MismatchedCollectionQueryUpdate" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MismatchedJavadocCode" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MismatchedStringBuilderQueryUpdate" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MissingOverrideAnnotation" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MissingRecentApi" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MissingSerialAnnotation" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MoveVariableDeclarationIntoWhen" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="MultipleVariablesInDeclaration" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="NegativeIntConstantInLongContext" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="NestedLambdaShadowedImplicitParameter" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="NewClassNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="NewObjectEquality" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="NoButtonGroup" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="NoButtonGroup" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="NoScrollPane" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="NoScrollPane" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="NonAtomicOperationOnVolatileField" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="StructuralWrap" enabled="false" level="TYPO" enabled_by_default="false" />
|
||||||
<inspection_tool class="NullChecksToSafeCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="NumberEquality" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ObjectEqualsCanBeEquality" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ObjectPrivatePropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ObjectsEqualsCanBeSimplified" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="OctalLiteral" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="OptionalAssignedToNull" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="OptionalExpectation" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="OptionalGetWithoutIsPresent" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="OptionalUsedAsFieldOrParameterType" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="PatternVariableHidesField" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="PlatformExtensionReceiverOfInline" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="PointlessBitwiseExpression" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="PresentationAnnotation" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="PrimitiveArrayArgumentToVariableArgMethod" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ProtectedInFinal" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="PsiElementConcatenation" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="PublicField" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantCompareToJavaTime" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantEmptyInitializerBlock" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantExplicitClose" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantFileCreation" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantGetter" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantLambdaOrAnonymousFunction" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantLengthCheck" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantNullableReturnType" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantObjectTypeCheck" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantRecordConstructor" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantScheduledForRemovalAnnotation" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantSemicolon" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantSuppression" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantSuspendModifier" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantThrows" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantUnitReturnType" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RedundantUnmodifiable" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RegExpDuplicateAlternationBranch" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RegExpEscapedMetaCharacter" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RegExpRedundantEscape" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RegExpSimplifiable" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RegExpSingleCharAlternation" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RegExpUnnecessaryNonCapturingGroup" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RemoveEmptyClassBody" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RemoveEmptyPrimaryConstructor" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RemoveToStringInStringTemplate" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ReplaceArrayOfWithLiteral" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ReplaceAssociateFunction" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ReplaceGuardClauseWithFunctionCall" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ReplaceJavaStaticMethodWithKotlinAnalog" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ReplaceRangeStartEndInclusiveWithFirstLast" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ReplaceRangeToWithRangeUntil" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ReplaceReadLineWithReadln" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ReplaceSizeZeroCheckWithIsEmpty" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ReplaceSubstringWithDropLast" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ReplaceWithEnumMap" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ReplaceWithOperatorAssignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="RequiredAttributes" enabled="false" level="WARNING" enabled_by_default="false">
|
|
||||||
<option name="myAdditionalRequiredHtmlAttributes" value="" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="ReturnFromFinallyBlock" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SafeCastWithReturn" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ScopeFunctionConversion" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SetterBackingFieldAssignment" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ShellCheck" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ShiftOutOfRange" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SimplifiableCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SimplifyBooleanWithConstants" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SimplifyCollector" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SimplifyWhenWithBooleanConstantCondition" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SingleElementAnnotation" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SingleStatementInBlock" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SortedCollectionWithNonComparableKeys" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SourceToSinkFlow" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="StatefulEp" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="StringBufferReplaceableByString" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="StringBufferReplaceableByStringBuilder" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="StringEquality" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="StringEqualsCharSequence" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SuspendFunctionOnCoroutineScope" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SuspiciousArrayMethodCall" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SuspiciousAsDynamic" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SuspiciousCollectionReassignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SuspiciousEqualsCombination" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SuspiciousListRemoveInLoop" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SuspiciousMethodCalls" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SuspiciousSystemArraycopy" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SuspiciousTernaryOperatorInVarargsCall" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SynchronizeOnNonFinalField" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="SystemRunFinalizersOnExit" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="TailRecursion" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="TestFailedLine" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="TestFunctionName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ThreadRun" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ThreadWithDefaultRunMethod" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ThrowFromFinallyBlock" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="ToArrayCallWithZeroLengthArrayArgument" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="TrailingWhitespacesInTextBlock" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="TryStatementWithMultipleResources" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="TypeCustomizer" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnaryPlus" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnknownLanguage" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnlabeledReturnInsideLambda" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnnecessaryBoxing" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnnecessaryCallToStringValueOf" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnnecessaryContinue" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnnecessaryLabelOnContinueStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnnecessaryLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnnecessaryOptInAnnotation" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnnecessaryUnaryMinus" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnnecessaryUnboxing" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnnecessaryUnicodeEscape" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UnusedMessageFormatParameter" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UseBulkOperation" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UseCompareMethod" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UseDPIAwareBorders" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UseDPIAwareInsets" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UsePluginIdEquals" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UsePrimitiveTypes" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="UselessCallOnCollection" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="VariableTypeCanBeExplicit" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="VerboseNullabilityAndEmptiness" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="WaitWhileHoldingTwoLocks" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="WhileCanBeForeach" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="WrongPackageStatement" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="WrongPropertyKeyValueDelimiter" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="XmlDefaultAttributeValue" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="XmlDeprecatedElement" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="XmlDuplicatedId" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="XmlUnusedNamespaceDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="XmlWrongRootElement" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="XsltDeclarations" enabled="false" level="ERROR" enabled_by_default="false" />
|
<inspection_tool class="XsltDeclarations" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||||
<inspection_tool class="XsltUnusedDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="XsltUnusedDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="XsltVariableShadowing" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="XsltVariableShadowing" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="YAMLDuplicatedKeys" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="YAMLRecursiveAlias" enabled="false" level="ERROR" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="YAMLSchemaValidation" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="IDE sample" type="GradleRunConfiguration" factoryName="Gradle">
|
<configuration default="false" name="IDE sample" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<log_file alias="idea.log" path="$PROJECT_DIR$/samples/ide-plugin/build/idea-sandbox/system/log/idea.log" />
|
||||||
<ExternalSystemSettings>
|
<ExternalSystemSettings>
|
||||||
<option name="executionName" />
|
<option name="executionName" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$/samples/ide-plugin" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$/samples/ide-plugin" />
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
<DebugAllEnabled>false</DebugAllEnabled>
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<RunAsTest>false</RunAsTest>
|
||||||
<method v="2" />
|
<method v="2" />
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
[](https://github.com/JetBrains#jetbrains-on-github) [](https://github.com/JetBrains/jewel/actions/workflows/build.yml)
|
[](https://github.com/JetBrains#jetbrains-on-github) [](https://github.com/JetBrains/jewel/actions/workflows/build.yml)
|
||||||
|
|
||||||
# Jewel: a Compose for Desktop theme
|
# Jewel: a Compose for Desktop theme
|
||||||
|
|
||||||
<img alt="Jewel logo" src="art/jewel-logo.svg" width="20%"/>
|
<img alt="Jewel logo" src="art/jewel-logo.svg" width="20%"/>
|
||||||
|
|
||||||
Jewel aims at recreating the _Darcula_ and _New UI_ Swing Look and Feels used on the IntelliJ Platform into Compose for Desktop.
|
Jewel aims at recreating the _Darcula_ and _New UI_ Swing Look and Feels used on the IntelliJ Platform into Compose for
|
||||||
|
Desktop.
|
||||||
|
|
||||||
> **Warning**
|
> **Warning**
|
||||||
>This project is in very early development and is probably not ready to be used in production projects. You _can_, but there
|
> This project is in very early development and is probably not ready to be used in production projects. You _can_, but
|
||||||
>are no published snapshots, and you should expect APIs to break fairly often, things to move around, and all that jazz.
|
> there
|
||||||
>Use at your risk!
|
> are no published snapshots, and you should expect APIs to break fairly often, things to move around, and all that
|
||||||
|
> jazz.
|
||||||
|
> Use at your risk!
|
||||||
|
|
||||||
## Project structure
|
## Project structure
|
||||||
|
|
||||||
@@ -19,7 +23,8 @@ The project is split in modules:
|
|||||||
3. `themes` are the two themes implemented by Jewel:
|
3. `themes` are the two themes implemented by Jewel:
|
||||||
1. `darcula` is the old school Intellij LaF, called Darcula, which has two implementations:
|
1. `darcula` is the old school Intellij LaF, called Darcula, which has two implementations:
|
||||||
1. `darcula-standalone` is the base theme and can be used in any Compose for Desktop project
|
1. `darcula-standalone` is the base theme and can be used in any Compose for Desktop project
|
||||||
2. `darcula-ide` is a version of the theme that can be used in an IDEA plugin, and integrates with the IDE's Swing LaF and themes via a
|
2. `darcula-ide` is a version of the theme that can be used in an IDEA plugin, and integrates with the IDE's
|
||||||
|
Swing LaF and themes via a
|
||||||
bridge (more
|
bridge (more
|
||||||
on that later)
|
on that later)
|
||||||
2. `new-ui` implements the new IntelliJ LaF, known as "new UI". This also has the same two implementations
|
2. `new-ui` implements the new IntelliJ LaF, known as "new UI". This also has the same two implementations
|
||||||
@@ -31,29 +36,45 @@ The project is split in modules:
|
|||||||
|
|
||||||
To run the stand-alone sample app, you can run the `:samples:standalone:run` Gradle task.
|
To run the stand-alone sample app, you can run the `:samples:standalone:run` Gradle task.
|
||||||
|
|
||||||
To run the IntelliJ IDEA plugin sample, you can run the `:samples:ide-plugin:runIde` Gradle task. This will download and run a copy of IJ Community
|
To run the IntelliJ IDEA plugin sample, you can run the `:samples:ide-plugin:runIde` Gradle task. This will download and
|
||||||
with the plugin installed; you can check the additional panels in the IDE once it starts up (at the bottom, by default, in old UI; in the overflow
|
run a copy of IJ Community
|
||||||
|
with the plugin installed; you can check the additional panels in the IDE once it starts up (at the bottom, by default,
|
||||||
|
in old UI; in the overflow
|
||||||
in the new UI).
|
in the new UI).
|
||||||
|
|
||||||
If you're using IntelliJ IDEA, you can use the "Stand-alone sample" and "IDE sample" run configurations.
|
If you're using IntelliJ IDEA, you can use the "Stand-alone sample" and "IDE sample" run configurations.
|
||||||
|
|
||||||
### The Swing Bridge
|
### The Swing Bridge
|
||||||
|
|
||||||
In the `*-ide` modules, there is a crucial element for proper integration with the IDE: a bridge between the Swing theme and LaF, and the Compose
|
In the `*-ide` modules, there is a crucial element for proper integration with the IDE: a bridge between the Swing theme
|
||||||
|
and LaF, and the Compose
|
||||||
world.
|
world.
|
||||||
This bridge ensures that we pick up the colours, typography, metrics, and images as defined in the current IntelliJ theme, and apply them to the
|
This bridge ensures that we pick up the colours, typography, metrics, and images as defined in the current IntelliJ
|
||||||
|
theme, and apply them to the
|
||||||
Compose theme as well.
|
Compose theme as well.
|
||||||
|
|
||||||
The work of building this bridge is fairly complex as there isn't a good mapping between the IDE LaF properties, the Darcula design specs, and the
|
The work of building this bridge is fairly complex as there isn't a good mapping between the IDE LaF properties, the
|
||||||
|
Darcula design specs, and the
|
||||||
Compose implementations. Sometimes, you will need to get a bit creative.
|
Compose implementations. Sometimes, you will need to get a bit creative.
|
||||||
|
|
||||||
When adding a new composable to the IJ theme, you need to make sure you also update the bridge to properly support it at runtime. You can refer to the
|
When adding a new composable to the IJ theme, you need to make sure you also update the bridge to properly support it at
|
||||||
[Darcula design specs](https://jetbrains.design/intellij) and corresponding [Figma specs](https://jetbrains.design/intellij/resources/UI_kit/), but
|
runtime. You can refer to the
|
||||||
the ultimate goal is consistency with the Swing implementation, so the ground truth of what you see in the IDE is the reference for any implementation
|
[Darcula design specs](https://jetbrains.design/intellij) and
|
||||||
|
corresponding [Figma specs](https://jetbrains.design/intellij/resources/UI_kit/), but
|
||||||
|
the ultimate goal is consistency with the Swing implementation, so the ground truth of what you see in the IDE is the
|
||||||
|
reference for any implementation
|
||||||
and trumps the specs.
|
and trumps the specs.
|
||||||
|
|
||||||
To find the required values in the IDE, we recommend enabling
|
To find the required values in the IDE, we recommend enabling
|
||||||
the [IDE internal mode](https://plugins.jetbrains.com/docs/intellij/enabling-internal.html)
|
the [IDE internal mode](https://plugins.jetbrains.com/docs/intellij/enabling-internal.html)
|
||||||
and using the [UI Inspector](https://plugins.jetbrains.com/docs/intellij/internal-ui-inspector.html) and
|
and using the [UI Inspector](https://plugins.jetbrains.com/docs/intellij/internal-ui-inspector.html) and
|
||||||
[LaF Defaults](https://plugins.jetbrains.com/docs/intellij/internal-ui-laf-defaults.html) tools to figure out the names of the parameters to use in
|
[LaF Defaults](https://plugins.jetbrains.com/docs/intellij/internal-ui-laf-defaults.html) tools to figure out the names
|
||||||
|
of the parameters to use in
|
||||||
the bridge.
|
the bridge.
|
||||||
|
|
||||||
|
To see debug logs in the IDE, add these to __Help | Diagnostic Tools | Debug Log Settings__:
|
||||||
|
|
||||||
|
```
|
||||||
|
#org.jetbrains.jewel.demo
|
||||||
|
#org.jetbrains.jewel
|
||||||
|
```
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ val sarif: Configuration by configurations.creating {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
sarif(projects.foundation)
|
|
||||||
sarif(projects.core)
|
sarif(projects.core)
|
||||||
sarif(projects.composeUtils)
|
sarif(projects.composeUtils)
|
||||||
sarif(projects.samples.standalone)
|
sarif(projects.samples.standalone)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ kotlin {
|
|||||||
optIn("kotlin.experimental.ExperimentalTypeInference")
|
optIn("kotlin.experimental.ExperimentalTypeInference")
|
||||||
optIn("androidx.compose.ui.ExperimentalComposeUiApi")
|
optIn("androidx.compose.ui.ExperimentalComposeUiApi")
|
||||||
optIn("androidx.compose.foundation.ExperimentalFoundationApi")
|
optIn("androidx.compose.foundation.ExperimentalFoundationApi")
|
||||||
|
optIn("org.jetbrains.jewel.ExperimentalJewelApi")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.jetbrains.jewel.buildlogic.theme
|
|||||||
|
|
||||||
import com.squareup.kotlinpoet.ClassName
|
import com.squareup.kotlinpoet.ClassName
|
||||||
import io.gitlab.arturbosch.detekt.Detekt
|
import io.gitlab.arturbosch.detekt.Detekt
|
||||||
import java.net.URL
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
@@ -23,7 +22,8 @@ import org.gradle.kotlin.dsl.register
|
|||||||
import org.gradle.kotlin.dsl.withType
|
import org.gradle.kotlin.dsl.withType
|
||||||
import org.gradle.util.internal.GUtil
|
import org.gradle.util.internal.GUtil
|
||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.BaseKotlinCompile
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
abstract class IntelliJThemeGeneratorPlugin : Plugin<Project> {
|
abstract class IntelliJThemeGeneratorPlugin : Plugin<Project> {
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ abstract class IntelliJThemeGeneratorPlugin : Plugin<Project> {
|
|||||||
ideaVersion.set(this@all.ideaVersion)
|
ideaVersion.set(this@all.ideaVersion)
|
||||||
themeFile.set(this@all.themeFile)
|
themeFile.set(this@all.themeFile)
|
||||||
}
|
}
|
||||||
tasks.withType<KotlinCompile> {
|
tasks.withType<BaseKotlinCompile> {
|
||||||
dependsOn(task)
|
dependsOn(task)
|
||||||
}
|
}
|
||||||
tasks.withType<Detekt> {
|
tasks.withType<Detekt> {
|
||||||
|
|||||||
@@ -7,6 +7,5 @@ dependencies {
|
|||||||
api(compose.desktop.currentOs) {
|
api(compose.desktop.currentOs) {
|
||||||
exclude(group = "org.jetbrains.compose.material")
|
exclude(group = "org.jetbrains.compose.material")
|
||||||
}
|
}
|
||||||
implementation(libs.jna)
|
|
||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,5 @@ plugins {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.composeUtils)
|
api(projects.composeUtils)
|
||||||
api(projects.foundation)
|
|
||||||
api(compose.desktop.common)
|
api(compose.desktop.common)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,9 @@ import androidx.compose.ui.semantics.Role
|
|||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Active
|
import org.jetbrains.jewel.CommonStateBitMask.Active
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Enabled
|
import org.jetbrains.jewel.CommonStateBitMask.Enabled
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Error
|
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Focused
|
import org.jetbrains.jewel.CommonStateBitMask.Focused
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Hovered
|
import org.jetbrains.jewel.CommonStateBitMask.Hovered
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Pressed
|
import org.jetbrains.jewel.CommonStateBitMask.Pressed
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Warning
|
|
||||||
import org.jetbrains.jewel.foundation.Stroke
|
import org.jetbrains.jewel.foundation.Stroke
|
||||||
import org.jetbrains.jewel.foundation.border
|
import org.jetbrains.jewel.foundation.border
|
||||||
import org.jetbrains.jewel.styling.ButtonStyle
|
import org.jetbrains.jewel.styling.ButtonStyle
|
||||||
@@ -55,7 +53,7 @@ fun DefaultButton(
|
|||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
style = style,
|
style = style,
|
||||||
content = content,
|
content = content,
|
||||||
textStyle = textStyle
|
textStyle = textStyle,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +74,7 @@ fun OutlinedButton(
|
|||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
style = style,
|
style = style,
|
||||||
content = content,
|
content = content,
|
||||||
textStyle = textStyle
|
textStyle = textStyle,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,23 +115,27 @@ private fun ButtonImpl(
|
|||||||
val shape = RoundedCornerShape(style.metrics.cornerSize)
|
val shape = RoundedCornerShape(style.metrics.cornerSize)
|
||||||
val colors = style.colors
|
val colors = style.colors
|
||||||
val borderColor by colors.borderFor(buttonState)
|
val borderColor by colors.borderFor(buttonState)
|
||||||
|
println("state: $buttonState ($enabled) -> $borderColor")
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier.clickable(
|
modifier = modifier
|
||||||
onClick = onClick,
|
.clickable(
|
||||||
enabled = enabled,
|
onClick = onClick,
|
||||||
role = Role.Button,
|
enabled = enabled,
|
||||||
interactionSource = interactionSource,
|
role = Role.Button,
|
||||||
indication = null
|
interactionSource = interactionSource,
|
||||||
).background(colors.backgroundFor(buttonState).value, shape)
|
indication = null,
|
||||||
|
)
|
||||||
|
.background(colors.backgroundFor(buttonState).value, shape)
|
||||||
.border(Stroke.Alignment.Center, style.metrics.borderWidth, borderColor, shape)
|
.border(Stroke.Alignment.Center, style.metrics.borderWidth, borderColor, shape)
|
||||||
.focusOutline(buttonState, shape),
|
.focusOutline(buttonState, shape),
|
||||||
propagateMinConstraints = true
|
propagateMinConstraints = true,
|
||||||
) {
|
) {
|
||||||
val contentColor by colors.contentFor(buttonState)
|
val contentColor by colors.contentFor(buttonState)
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalContentColor provides contentColor.takeOrElse { textStyle.color },
|
LocalContentColor provides contentColor.takeOrElse { textStyle.color },
|
||||||
LocalTextStyle provides textStyle.copy(color = contentColor.takeOrElse { textStyle.color })
|
LocalTextStyle provides textStyle.copy(color = contentColor.takeOrElse { textStyle.color }),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
@@ -141,7 +143,7 @@ private fun ButtonImpl(
|
|||||||
.padding(style.metrics.padding),
|
.padding(style.metrics.padding),
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
content = content
|
content = content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,7 +184,7 @@ value class ButtonState(val state: ULong) : FocusableComponentState {
|
|||||||
focused = focused,
|
focused = focused,
|
||||||
pressed = pressed,
|
pressed = pressed,
|
||||||
hovered = hovered,
|
hovered = hovered,
|
||||||
active = active
|
active = active,
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun toString() =
|
override fun toString() =
|
||||||
@@ -194,19 +196,15 @@ value class ButtonState(val state: ULong) : FocusableComponentState {
|
|||||||
fun of(
|
fun of(
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
focused: Boolean = false,
|
focused: Boolean = false,
|
||||||
error: Boolean = false,
|
|
||||||
pressed: Boolean = false,
|
pressed: Boolean = false,
|
||||||
hovered: Boolean = false,
|
hovered: Boolean = false,
|
||||||
warning: Boolean = false,
|
|
||||||
active: Boolean = true,
|
active: Boolean = true,
|
||||||
) = ButtonState(
|
) = ButtonState(
|
||||||
state = (if (enabled) Enabled else 0UL) or
|
state = (if (enabled) Enabled else 0UL) or
|
||||||
(if (focused) Focused else 0UL) or
|
(if (focused) Focused else 0UL) or
|
||||||
(if (hovered) Hovered else 0UL) or
|
(if (hovered) Hovered else 0UL) or
|
||||||
(if (pressed) Pressed else 0UL) or
|
(if (pressed) Pressed else 0UL) or
|
||||||
(if (warning) Warning else 0UL) or
|
(if (active) Active else 0UL),
|
||||||
(if (error) Error else 0UL) or
|
|
||||||
(if (active) Active else 0UL)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ fun Checkbox(
|
|||||||
icons = icons,
|
icons = icons,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
resourceLoader = resourceLoader,
|
resourceLoader = resourceLoader,
|
||||||
content = null
|
content = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ fun TriStateCheckbox(
|
|||||||
icons = icons,
|
icons = icons,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
resourceLoader = resourceLoader,
|
resourceLoader = resourceLoader,
|
||||||
content = null
|
content = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ fun TriStateCheckboxRow(
|
|||||||
metrics = metrics,
|
metrics = metrics,
|
||||||
icons = icons,
|
icons = icons,
|
||||||
resourceLoader = resourceLoader,
|
resourceLoader = resourceLoader,
|
||||||
textStyle = textStyle
|
textStyle = textStyle,
|
||||||
) {
|
) {
|
||||||
Text(text)
|
Text(text)
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,7 @@ fun CheckboxRow(
|
|||||||
metrics = metrics,
|
metrics = metrics,
|
||||||
icons = icons,
|
icons = icons,
|
||||||
resourceLoader = resourceLoader,
|
resourceLoader = resourceLoader,
|
||||||
textStyle = textStyle
|
textStyle = textStyle,
|
||||||
) {
|
) {
|
||||||
Text(text)
|
Text(text)
|
||||||
}
|
}
|
||||||
@@ -200,7 +200,7 @@ fun CheckboxRow(
|
|||||||
icons = icons,
|
icons = icons,
|
||||||
resourceLoader = resourceLoader,
|
resourceLoader = resourceLoader,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
content = content
|
content = content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ fun TriStateCheckboxRow(
|
|||||||
icons = icons,
|
icons = icons,
|
||||||
resourceLoader = resourceLoader,
|
resourceLoader = resourceLoader,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
content = content
|
content = content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,16 +280,16 @@ private fun CheckboxImpl(
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
role = Role.Checkbox,
|
role = Role.Checkbox,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
indication = null
|
indication = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
val checkBoxImageModifier = Modifier.size(metrics.checkboxSize)
|
val checkBoxImageModifier = Modifier.size(metrics.checkboxSize)
|
||||||
.outline(
|
.outline(
|
||||||
state = checkboxState,
|
state = checkboxState,
|
||||||
outline = outline,
|
outline = outline,
|
||||||
alignment = Stroke.Alignment.Center,
|
|
||||||
outlineShape = RoundedCornerShape(metrics.checkboxCornerSize),
|
outlineShape = RoundedCornerShape(metrics.checkboxCornerSize),
|
||||||
outlineWidth = metrics.outlineWidth
|
alignment = Stroke.Alignment.Center,
|
||||||
|
outlineWidth = metrics.outlineWidth,
|
||||||
)
|
)
|
||||||
|
|
||||||
val checkboxPainter by icons.checkbox.getPainter(checkboxState, resourceLoader)
|
val checkboxPainter by icons.checkbox.getPainter(checkboxState, resourceLoader)
|
||||||
@@ -300,14 +300,14 @@ private fun CheckboxImpl(
|
|||||||
Row(
|
Row(
|
||||||
wrapperModifier,
|
wrapperModifier,
|
||||||
horizontalArrangement = Arrangement.spacedBy(metrics.iconContentGap),
|
horizontalArrangement = Arrangement.spacedBy(metrics.iconContentGap),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
CheckBoxImage(Modifier, checkboxPainter, checkBoxImageModifier)
|
CheckBoxImage(Modifier, checkboxPainter, checkBoxImageModifier)
|
||||||
|
|
||||||
val contentColor by colors.contentFor(checkboxState)
|
val contentColor by colors.contentFor(checkboxState)
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalTextStyle provides textStyle.copy(color = contentColor.takeOrElse { textStyle.color }),
|
LocalTextStyle provides textStyle.copy(color = contentColor.takeOrElse { textStyle.color }),
|
||||||
LocalContentColor provides contentColor.takeOrElse { textStyle.color }
|
LocalContentColor provides contentColor.takeOrElse { textStyle.color },
|
||||||
) {
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
@@ -367,7 +367,7 @@ value class CheckboxState(private val state: ULong) : ToggleableComponentState {
|
|||||||
focused = focused,
|
focused = focused,
|
||||||
pressed = pressed,
|
pressed = pressed,
|
||||||
hovered = hovered,
|
hovered = hovered,
|
||||||
active = active
|
active = active,
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun toString() =
|
override fun toString() =
|
||||||
@@ -390,7 +390,7 @@ value class CheckboxState(private val state: ULong) : ToggleableComponentState {
|
|||||||
(if (pressed) Pressed else 0UL) or
|
(if (pressed) Pressed else 0UL) or
|
||||||
(if (toggleableState != ToggleableState.Off) Selected else 0UL) or
|
(if (toggleableState != ToggleableState.Off) Selected else 0UL) or
|
||||||
(if (toggleableState == ToggleableState.Indeterminate) Indeterminate else 0UL) or
|
(if (toggleableState == ToggleableState.Indeterminate) Indeterminate else 0UL) or
|
||||||
(if (active) Active else 0UL)
|
(if (active) Active else 0UL),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.interaction.PressInteraction
|
import androidx.compose.foundation.interaction.PressInteraction
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.selection.selectable
|
||||||
|
import androidx.compose.foundation.selection.toggleable
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
@@ -26,11 +27,10 @@ import androidx.compose.ui.graphics.isUnspecified
|
|||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Active
|
import org.jetbrains.jewel.CommonStateBitMask.Active
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Enabled
|
import org.jetbrains.jewel.CommonStateBitMask.Enabled
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Error
|
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Focused
|
import org.jetbrains.jewel.CommonStateBitMask.Focused
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Hovered
|
import org.jetbrains.jewel.CommonStateBitMask.Hovered
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Pressed
|
import org.jetbrains.jewel.CommonStateBitMask.Pressed
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Warning
|
import org.jetbrains.jewel.CommonStateBitMask.Selected
|
||||||
import org.jetbrains.jewel.foundation.Stroke
|
import org.jetbrains.jewel.foundation.Stroke
|
||||||
import org.jetbrains.jewel.foundation.border
|
import org.jetbrains.jewel.foundation.border
|
||||||
import org.jetbrains.jewel.styling.ChipStyle
|
import org.jetbrains.jewel.styling.ChipStyle
|
||||||
@@ -40,15 +40,95 @@ fun Chip(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
|
selected: Boolean = false,
|
||||||
style: ChipStyle = IntelliJTheme.chipStyle,
|
style: ChipStyle = IntelliJTheme.chipStyle,
|
||||||
onChipClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
ChipImpl(
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
enabled = enabled,
|
||||||
|
selected = selected,
|
||||||
|
style = style,
|
||||||
|
modifier = modifier.clickable(
|
||||||
|
onClick = onClick,
|
||||||
|
enabled = enabled,
|
||||||
|
role = Role.Button,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = null,
|
||||||
|
),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ToggleableChip(
|
||||||
|
checked: Boolean,
|
||||||
|
onClick: (Boolean) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
enabled: Boolean = true,
|
||||||
|
style: ChipStyle = IntelliJTheme.chipStyle,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
ChipImpl(
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
enabled = enabled,
|
||||||
|
selected = checked,
|
||||||
|
style = style,
|
||||||
|
modifier = modifier.toggleable(
|
||||||
|
onValueChange = onClick,
|
||||||
|
enabled = enabled,
|
||||||
|
role = Role.Checkbox,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = null,
|
||||||
|
value = checked,
|
||||||
|
),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RadioButtonChip(
|
||||||
|
selected: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
enabled: Boolean = true,
|
||||||
|
style: ChipStyle = IntelliJTheme.chipStyle,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
ChipImpl(
|
||||||
|
interactionSource,
|
||||||
|
enabled,
|
||||||
|
selected,
|
||||||
|
style,
|
||||||
|
modifier.selectable(
|
||||||
|
onClick = onClick,
|
||||||
|
enabled = enabled,
|
||||||
|
role = Role.RadioButton,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = null,
|
||||||
|
selected = selected,
|
||||||
|
),
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ChipImpl(
|
||||||
|
interactionSource: MutableInteractionSource,
|
||||||
|
enabled: Boolean,
|
||||||
|
selected: Boolean,
|
||||||
|
style: ChipStyle,
|
||||||
|
modifier: Modifier,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
var chipState by remember(interactionSource) {
|
var chipState by remember(interactionSource) {
|
||||||
mutableStateOf(ChipState.of(enabled = enabled))
|
mutableStateOf(ChipState.of(enabled = enabled, selected = selected))
|
||||||
}
|
}
|
||||||
remember(enabled) {
|
remember(enabled, selected) {
|
||||||
chipState = chipState.copy(enabled = enabled)
|
chipState = chipState.copy(enabled = enabled, selected = selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(interactionSource) {
|
LaunchedEffect(interactionSource) {
|
||||||
@@ -70,20 +150,12 @@ fun Chip(
|
|||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clickable(
|
|
||||||
onClick = onChipClick,
|
|
||||||
enabled = enabled,
|
|
||||||
role = Role.Button,
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
indication = null
|
|
||||||
)
|
|
||||||
.padding(style.metrics.padding)
|
|
||||||
.defaultMinSize(style.metrics.minSize.width, style.metrics.minSize.height)
|
|
||||||
.background(colors.backgroundFor(chipState).value, shape)
|
.background(colors.backgroundFor(chipState).value, shape)
|
||||||
.border(Stroke.Alignment.Center, style.metrics.borderWidth, borderColor, shape)
|
.border(Stroke.Alignment.Center, style.metrics.borderWidth, borderColor, shape)
|
||||||
.outline(chipState, shape),
|
.focusOutline(chipState, shape)
|
||||||
|
.padding(style.metrics.padding),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Center
|
horizontalArrangement = Arrangement.Center,
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalContentColor provides (
|
LocalContentColor provides (
|
||||||
@@ -92,7 +164,7 @@ fun Chip(
|
|||||||
.value
|
.value
|
||||||
.takeIf { !it.isUnspecified }
|
.takeIf { !it.isUnspecified }
|
||||||
?: LocalContentColor.current
|
?: LocalContentColor.current
|
||||||
)
|
),
|
||||||
) {
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
@@ -101,7 +173,7 @@ fun Chip(
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class ChipState(val state: ULong) : StateWithOutline {
|
value class ChipState(val state: ULong) : FocusableComponentState, SelectableComponentState {
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
override val isActive: Boolean
|
override val isActive: Boolean
|
||||||
@@ -116,12 +188,8 @@ value class ChipState(val state: ULong) : StateWithOutline {
|
|||||||
get() = state and Focused != 0UL
|
get() = state and Focused != 0UL
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
override val isError: Boolean
|
override val isSelected: Boolean
|
||||||
get() = state and Error != 0UL
|
get() = state and Selected != 0UL
|
||||||
|
|
||||||
@Stable
|
|
||||||
override val isWarning: Boolean
|
|
||||||
get() = state and Warning != 0UL
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
override val isHovered: Boolean
|
override val isHovered: Boolean
|
||||||
@@ -134,23 +202,21 @@ value class ChipState(val state: ULong) : StateWithOutline {
|
|||||||
fun copy(
|
fun copy(
|
||||||
enabled: Boolean = isEnabled,
|
enabled: Boolean = isEnabled,
|
||||||
focused: Boolean = isFocused,
|
focused: Boolean = isFocused,
|
||||||
error: Boolean = isError,
|
selected: Boolean = isSelected,
|
||||||
pressed: Boolean = isPressed,
|
pressed: Boolean = isPressed,
|
||||||
hovered: Boolean = isHovered,
|
hovered: Boolean = isHovered,
|
||||||
warning: Boolean = isWarning,
|
|
||||||
active: Boolean = isActive,
|
active: Boolean = isActive,
|
||||||
): ChipState = of(
|
): ChipState = of(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
focused = focused,
|
focused = focused,
|
||||||
error = error,
|
|
||||||
pressed = pressed,
|
pressed = pressed,
|
||||||
hovered = hovered,
|
hovered = hovered,
|
||||||
warning = warning,
|
active = active,
|
||||||
active = active
|
selected = selected,
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun toString() =
|
override fun toString() =
|
||||||
"ChipState(isEnabled=$isEnabled, isFocused=$isFocused, isError=$isError, isWarning=$isWarning, " +
|
"ChipState(isEnabled=$isEnabled, isFocused=$isFocused, isSelected=$isSelected, " +
|
||||||
"isHovered=$isHovered, isPressed=$isPressed, isActive=$isActive)"
|
"isHovered=$isHovered, isPressed=$isPressed, isActive=$isActive)"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -158,19 +224,17 @@ value class ChipState(val state: ULong) : StateWithOutline {
|
|||||||
fun of(
|
fun of(
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
focused: Boolean = false,
|
focused: Boolean = false,
|
||||||
error: Boolean = false,
|
selected: Boolean = false,
|
||||||
pressed: Boolean = false,
|
pressed: Boolean = false,
|
||||||
hovered: Boolean = false,
|
hovered: Boolean = false,
|
||||||
warning: Boolean = false,
|
|
||||||
active: Boolean = false,
|
active: Boolean = false,
|
||||||
) = ChipState(
|
) = ChipState(
|
||||||
state = (if (enabled) Enabled else 0UL) or
|
state = (if (enabled) Enabled else 0UL) or
|
||||||
(if (focused) Focused else 0UL) or
|
(if (focused) Focused else 0UL) or
|
||||||
(if (error) Error else 0UL) or
|
(if (selected) Selected else 0UL) or
|
||||||
(if (hovered) Hovered else 0UL) or
|
(if (hovered) Hovered else 0UL) or
|
||||||
(if (pressed) Pressed else 0UL) or
|
(if (pressed) Pressed else 0UL) or
|
||||||
(if (warning) Warning else 0UL) or
|
(if (active) Active else 0UL),
|
||||||
(if (active) Active else 0UL)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import androidx.compose.ui.input.InputMode
|
|||||||
import androidx.compose.ui.input.InputModeManager
|
import androidx.compose.ui.input.InputModeManager
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.LocalInputModeManager
|
import androidx.compose.ui.platform.LocalInputModeManager
|
||||||
|
import androidx.compose.ui.res.ResourceLoader
|
||||||
import androidx.compose.ui.window.Popup
|
import androidx.compose.ui.window.Popup
|
||||||
|
import androidx.compose.ui.window.PopupProperties
|
||||||
import androidx.compose.ui.window.rememberCursorPositionProvider
|
import androidx.compose.ui.window.rememberCursorPositionProvider
|
||||||
import org.jetbrains.jewel.styling.MenuStyle
|
import org.jetbrains.jewel.styling.MenuStyle
|
||||||
|
|
||||||
@@ -31,7 +33,8 @@ object IntelliJContextMenuRepresentation : ContextMenuRepresentation {
|
|||||||
state.status = ContextMenuState.Status.Closed
|
state.status = ContextMenuState.Status.Closed
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
style = IntelliJTheme.menuStyle
|
style = IntelliJTheme.menuStyle,
|
||||||
|
resourceLoader = LocalResourceLoader.current,
|
||||||
) {
|
) {
|
||||||
contextItems(items)
|
contextItems(items)
|
||||||
}
|
}
|
||||||
@@ -42,8 +45,9 @@ object IntelliJContextMenuRepresentation : ContextMenuRepresentation {
|
|||||||
@Composable
|
@Composable
|
||||||
internal fun ContextMenu(
|
internal fun ContextMenu(
|
||||||
onDismissRequest: (InputMode) -> Boolean,
|
onDismissRequest: (InputMode) -> Boolean,
|
||||||
focusable: Boolean = true,
|
resourceLoader: ResourceLoader,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
focusable: Boolean = true,
|
||||||
style: MenuStyle = IntelliJTheme.menuStyle,
|
style: MenuStyle = IntelliJTheme.menuStyle,
|
||||||
content: MenuScope.() -> Unit,
|
content: MenuScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -51,31 +55,33 @@ internal fun ContextMenu(
|
|||||||
var inputModeManager: InputModeManager? by mutableStateOf(null)
|
var inputModeManager: InputModeManager? by mutableStateOf(null)
|
||||||
val menuManager = remember(onDismissRequest) {
|
val menuManager = remember(onDismissRequest) {
|
||||||
MenuManager(
|
MenuManager(
|
||||||
onDismissRequest = onDismissRequest
|
onDismissRequest = onDismissRequest,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Popup(
|
Popup(
|
||||||
focusable = focusable,
|
popupPositionProvider = rememberCursorPositionProvider(style.metrics.offset),
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
onDismissRequest(InputMode.Touch)
|
onDismissRequest(InputMode.Touch)
|
||||||
},
|
},
|
||||||
popupPositionProvider = rememberCursorPositionProvider(style.metrics.offset),
|
properties = PopupProperties(focusable = focusable),
|
||||||
|
onPreviewKeyEvent = { false },
|
||||||
onKeyEvent = {
|
onKeyEvent = {
|
||||||
val currentFocusManager = checkNotNull(focusManager) { "FocusManager must not be null" }
|
val currentFocusManager = checkNotNull(focusManager) { "FocusManager must not be null" }
|
||||||
val currentInputModeManager = checkNotNull(inputModeManager) { "InputModeManager must not be null" }
|
val currentInputModeManager = checkNotNull(inputModeManager) { "InputModeManager must not be null" }
|
||||||
handlePopupMenuOnKeyEvent(it, currentFocusManager, currentInputModeManager, menuManager)
|
handlePopupMenuOnKeyEvent(it, currentFocusManager, currentInputModeManager, menuManager)
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
focusManager = LocalFocusManager.current
|
focusManager = LocalFocusManager.current
|
||||||
inputModeManager = LocalInputModeManager.current
|
inputModeManager = LocalInputModeManager.current
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalMenuManager provides menuManager
|
LocalMenuManager provides menuManager,
|
||||||
) {
|
) {
|
||||||
MenuContent(
|
MenuContent(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
content = content
|
content = content,
|
||||||
|
resourceLoader = resourceLoader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +91,7 @@ private fun MenuScope.contextItems(items: () -> List<ContextMenuItem>) {
|
|||||||
items().forEach { item ->
|
items().forEach { item ->
|
||||||
when (item) {
|
when (item) {
|
||||||
is ContextMenuDivider -> {
|
is ContextMenuDivider -> {
|
||||||
divider()
|
separator()
|
||||||
}
|
}
|
||||||
|
|
||||||
is ContextSubmenu -> {
|
is ContextSubmenu -> {
|
||||||
@@ -99,7 +105,7 @@ private fun MenuScope.contextItems(items: () -> List<ContextMenuItem>) {
|
|||||||
else -> {
|
else -> {
|
||||||
selectableItem(
|
selectableItem(
|
||||||
selected = false,
|
selected = false,
|
||||||
onClick = item.onClick
|
onClick = item.onClick,
|
||||||
) {
|
) {
|
||||||
Text(item.label)
|
Text(item.label)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.jetbrains.jewel
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.graphics.ColorMatrix
|
||||||
|
|
||||||
|
// Implements javax.swing.GrayFilter's behaviour with percent = 50, brighter = true
|
||||||
|
// to match the GrayFilter#createDisabledImage behavior, used by Swing.
|
||||||
|
private val disabledColorMatrixGammaEncoded = ColorMatrix().apply {
|
||||||
|
val saturation = .5f
|
||||||
|
|
||||||
|
// We use NTSC luminance weights like Swing does as it's gamma-encoded RGB
|
||||||
|
val redFactor = .299f * saturation
|
||||||
|
val greenFactor = .587f * saturation
|
||||||
|
val blueFactor = .114f * saturation
|
||||||
|
|
||||||
|
// TODO we should also be scaling the brightness but it's not possible
|
||||||
|
// with a matrix transformation as far as I can tell
|
||||||
|
this[0, 0] = redFactor
|
||||||
|
this[0, 1] = greenFactor
|
||||||
|
this[0, 2] = blueFactor
|
||||||
|
this[1, 0] = redFactor
|
||||||
|
this[1, 1] = greenFactor
|
||||||
|
this[1, 2] = blueFactor
|
||||||
|
this[2, 0] = redFactor
|
||||||
|
this[2, 1] = greenFactor
|
||||||
|
this[2, 2] = blueFactor
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColorFilter.Companion.disabled(): ColorFilter = colorMatrix(disabledColorMatrixGammaEncoded)
|
||||||
@@ -36,6 +36,6 @@ fun Divider(
|
|||||||
modifier
|
modifier
|
||||||
.then(indentMod)
|
.then(indentMod)
|
||||||
.then(orientationModifier)
|
.then(orientationModifier)
|
||||||
.background(color = color)
|
.background(color = color),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,13 +34,12 @@ import androidx.compose.ui.platform.LocalInputModeManager
|
|||||||
import androidx.compose.ui.res.ResourceLoader
|
import androidx.compose.ui.res.ResourceLoader
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.window.Popup
|
import androidx.compose.ui.window.Popup
|
||||||
|
import androidx.compose.ui.window.PopupProperties
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Active
|
import org.jetbrains.jewel.CommonStateBitMask.Active
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Enabled
|
import org.jetbrains.jewel.CommonStateBitMask.Enabled
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Error
|
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Focused
|
import org.jetbrains.jewel.CommonStateBitMask.Focused
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Hovered
|
import org.jetbrains.jewel.CommonStateBitMask.Hovered
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Pressed
|
import org.jetbrains.jewel.CommonStateBitMask.Pressed
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Warning
|
|
||||||
import org.jetbrains.jewel.foundation.Stroke
|
import org.jetbrains.jewel.foundation.Stroke
|
||||||
import org.jetbrains.jewel.foundation.border
|
import org.jetbrains.jewel.foundation.border
|
||||||
import org.jetbrains.jewel.styling.DropdownStyle
|
import org.jetbrains.jewel.styling.DropdownStyle
|
||||||
@@ -64,11 +63,11 @@ fun Dropdown(
|
|||||||
var skipNextClick by remember { mutableStateOf(false) }
|
var skipNextClick by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
var dropdownState by remember(interactionSource) {
|
var dropdownState by remember(interactionSource) {
|
||||||
mutableStateOf(DropdownState.of(enabled = enabled, outline = outline))
|
mutableStateOf(DropdownState.of(enabled = enabled))
|
||||||
}
|
}
|
||||||
|
|
||||||
remember(enabled, outline) {
|
remember(enabled) {
|
||||||
dropdownState = dropdownState.copy(enabled = enabled, outline = Outline.Error)
|
dropdownState = dropdownState.copy(enabled = enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(interactionSource) {
|
LaunchedEffect(interactionSource) {
|
||||||
@@ -91,6 +90,7 @@ fun Dropdown(
|
|||||||
val metrics = style.metrics
|
val metrics = style.metrics
|
||||||
val shape = RoundedCornerShape(style.metrics.cornerSize)
|
val shape = RoundedCornerShape(style.metrics.cornerSize)
|
||||||
val minSize = metrics.minSize
|
val minSize = metrics.minSize
|
||||||
|
val arrowMinSize = style.metrics.arrowMinSize
|
||||||
val borderColor by colors.borderFor(dropdownState)
|
val borderColor by colors.borderFor(dropdownState)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
@@ -105,33 +105,37 @@ fun Dropdown(
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
indication = null
|
indication = null,
|
||||||
).background(colors.backgroundFor(dropdownState).value, shape)
|
)
|
||||||
|
.background(colors.backgroundFor(dropdownState).value, shape)
|
||||||
.border(Stroke.Alignment.Center, style.metrics.borderWidth, borderColor, shape)
|
.border(Stroke.Alignment.Center, style.metrics.borderWidth, borderColor, shape)
|
||||||
.defaultMinSize(minSize.width, minSize.height),
|
.outline(dropdownState, outline, shape)
|
||||||
contentAlignment = Alignment.CenterStart
|
.defaultMinSize(minSize.width, minSize.height.coerceAtLeast(arrowMinSize.height)),
|
||||||
|
contentAlignment = Alignment.CenterStart,
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalContentColor provides colors.contentFor(dropdownState).value
|
LocalContentColor provides colors.contentFor(dropdownState).value,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
Modifier.padding(style.metrics.contentPadding).padding(end = minSize.height),
|
modifier = Modifier.padding(style.metrics.contentPadding)
|
||||||
|
.padding(end = minSize.height),
|
||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
content = {
|
content = {
|
||||||
content()
|
content()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.size(minSize.height).align(Alignment.CenterEnd),
|
modifier = Modifier.size(arrowMinSize)
|
||||||
contentAlignment = Alignment.Center
|
.align(Alignment.CenterEnd),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
val chevronIcon by style.icons.chevronDown.getPainter(dropdownState, resourceLoader)
|
val chevronIcon by style.icons.chevronDown.getPainter(dropdownState, resourceLoader)
|
||||||
Icon(
|
Icon(
|
||||||
painter = chevronIcon,
|
painter = chevronIcon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = colors.iconTintFor(dropdownState).value
|
tint = colors.iconTintFor(dropdownState).value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +153,8 @@ fun Dropdown(
|
|||||||
modifier = menuModifier,
|
modifier = menuModifier,
|
||||||
style = style.menuStyle,
|
style = style.menuStyle,
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
content = menuContent
|
content = menuContent,
|
||||||
|
resourceLoader = resourceLoader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,6 +164,7 @@ fun Dropdown(
|
|||||||
internal fun DropdownMenu(
|
internal fun DropdownMenu(
|
||||||
onDismissRequest: (InputMode) -> Boolean,
|
onDismissRequest: (InputMode) -> Boolean,
|
||||||
horizontalAlignment: Alignment.Horizontal,
|
horizontalAlignment: Alignment.Horizontal,
|
||||||
|
resourceLoader: ResourceLoader,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
style: MenuStyle,
|
style: MenuStyle,
|
||||||
content: MenuScope.() -> Unit,
|
content: MenuScope.() -> Unit,
|
||||||
@@ -167,9 +173,9 @@ internal fun DropdownMenu(
|
|||||||
|
|
||||||
val popupPositionProvider = AnchorVerticalMenuPositionProvider(
|
val popupPositionProvider = AnchorVerticalMenuPositionProvider(
|
||||||
contentOffset = style.metrics.offset,
|
contentOffset = style.metrics.offset,
|
||||||
contentMargin = style.metrics.margin,
|
contentMargin = style.metrics.menuMargin,
|
||||||
alignment = horizontalAlignment,
|
alignment = horizontalAlignment,
|
||||||
density = density
|
density = density,
|
||||||
)
|
)
|
||||||
|
|
||||||
var focusManager: FocusManager? by mutableStateOf(null)
|
var focusManager: FocusManager? by mutableStateOf(null)
|
||||||
@@ -179,25 +185,27 @@ internal fun DropdownMenu(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Popup(
|
Popup(
|
||||||
onDismissRequest = { onDismissRequest(InputMode.Touch) },
|
|
||||||
popupPositionProvider = popupPositionProvider,
|
popupPositionProvider = popupPositionProvider,
|
||||||
|
onDismissRequest = { onDismissRequest(InputMode.Touch) },
|
||||||
|
properties = PopupProperties(focusable = true),
|
||||||
|
onPreviewKeyEvent = { false },
|
||||||
onKeyEvent = {
|
onKeyEvent = {
|
||||||
val currentFocusManager = checkNotNull(focusManager) { "FocusManager must not be null" }
|
val currentFocusManager = checkNotNull(focusManager) { "FocusManager must not be null" }
|
||||||
val currentInputModeManager = checkNotNull(inputModeManager) { "InputModeManager must not be null" }
|
val currentInputModeManager = checkNotNull(inputModeManager) { "InputModeManager must not be null" }
|
||||||
handlePopupMenuOnKeyEvent(it, currentFocusManager, currentInputModeManager, menuManager)
|
handlePopupMenuOnKeyEvent(it, currentFocusManager, currentInputModeManager, menuManager)
|
||||||
},
|
},
|
||||||
focusable = true
|
|
||||||
) {
|
) {
|
||||||
focusManager = LocalFocusManager.current
|
focusManager = LocalFocusManager.current
|
||||||
inputModeManager = LocalInputModeManager.current
|
inputModeManager = LocalInputModeManager.current
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalMenuManager provides menuManager,
|
LocalMenuManager provides menuManager,
|
||||||
LocalMenuStyle provides style
|
LocalMenuStyle provides style,
|
||||||
) {
|
) {
|
||||||
MenuContent(
|
MenuContent(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
content = content
|
content = content,
|
||||||
|
resourceLoader = resourceLoader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,7 +213,7 @@ internal fun DropdownMenu(
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class DropdownState(val state: ULong) : StateWithOutline {
|
value class DropdownState(val state: ULong) : FocusableComponentState {
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
override val isActive: Boolean
|
override val isActive: Boolean
|
||||||
@@ -219,14 +227,6 @@ value class DropdownState(val state: ULong) : StateWithOutline {
|
|||||||
override val isFocused: Boolean
|
override val isFocused: Boolean
|
||||||
get() = state and Focused != 0UL
|
get() = state and Focused != 0UL
|
||||||
|
|
||||||
@Stable
|
|
||||||
override val isError: Boolean
|
|
||||||
get() = state and Error != 0UL
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
override val isWarning: Boolean
|
|
||||||
get() = state and Warning != 0UL
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
override val isHovered: Boolean
|
override val isHovered: Boolean
|
||||||
get() = state and Hovered != 0UL
|
get() = state and Hovered != 0UL
|
||||||
@@ -240,19 +240,17 @@ value class DropdownState(val state: ULong) : StateWithOutline {
|
|||||||
focused: Boolean = isFocused,
|
focused: Boolean = isFocused,
|
||||||
pressed: Boolean = isPressed,
|
pressed: Boolean = isPressed,
|
||||||
hovered: Boolean = isHovered,
|
hovered: Boolean = isHovered,
|
||||||
outline: Outline = Outline.of(isWarning, isError),
|
|
||||||
active: Boolean = isActive,
|
active: Boolean = isActive,
|
||||||
) = of(
|
) = of(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
focused = focused,
|
focused = focused,
|
||||||
pressed = pressed,
|
pressed = pressed,
|
||||||
hovered = hovered,
|
hovered = hovered,
|
||||||
outline = outline,
|
active = active,
|
||||||
active = active
|
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun toString() =
|
override fun toString() =
|
||||||
"${javaClass.simpleName}(isEnabled=$isEnabled, isFocused=$isFocused, isError=$isError, isWarning=$isWarning, " +
|
"${javaClass.simpleName}(isEnabled=$isEnabled, isFocused=$isFocused, " +
|
||||||
"isHovered=$isHovered, isPressed=$isPressed, isActive=$isActive)"
|
"isHovered=$isHovered, isPressed=$isPressed, isActive=$isActive)"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -262,7 +260,6 @@ value class DropdownState(val state: ULong) : StateWithOutline {
|
|||||||
focused: Boolean = false,
|
focused: Boolean = false,
|
||||||
pressed: Boolean = false,
|
pressed: Boolean = false,
|
||||||
hovered: Boolean = false,
|
hovered: Boolean = false,
|
||||||
outline: Outline = Outline.None,
|
|
||||||
active: Boolean = false,
|
active: Boolean = false,
|
||||||
) = DropdownState(
|
) = DropdownState(
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
@@ -272,10 +269,8 @@ value class DropdownState(val state: ULong) : StateWithOutline {
|
|||||||
(if (focused) Focused else 0UL) or
|
(if (focused) Focused else 0UL) or
|
||||||
(if (pressed) Pressed else 0UL) or
|
(if (pressed) Pressed else 0UL) or
|
||||||
(if (hovered) Hovered else 0UL) or
|
(if (hovered) Hovered else 0UL) or
|
||||||
(if (outline == Outline.Error) Error else 0UL) or
|
|
||||||
(if (outline == Outline.Warning) Warning else 0UL) or
|
|
||||||
(if (active) Active else 0UL)
|
(if (active) Active else 0UL)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package org.jetbrains.jewel
|
|||||||
@RequiresOptIn(
|
@RequiresOptIn(
|
||||||
level = RequiresOptIn.Level.WARNING,
|
level = RequiresOptIn.Level.WARNING,
|
||||||
message = "This is an experimental API for Jewel and is likely to change before becoming " +
|
message = "This is an experimental API for Jewel and is likely to change before becoming " +
|
||||||
"stable."
|
"stable.",
|
||||||
)
|
)
|
||||||
@Target(
|
@Target(
|
||||||
AnnotationTarget.CLASS,
|
AnnotationTarget.CLASS,
|
||||||
AnnotationTarget.FUNCTION
|
AnnotationTarget.FUNCTION,
|
||||||
)
|
)
|
||||||
annotation class ExperimentalJewelApi
|
annotation class ExperimentalJewelApi
|
||||||
@@ -8,41 +8,33 @@ import androidx.compose.ui.graphics.Color
|
|||||||
interface GlobalColors {
|
interface GlobalColors {
|
||||||
|
|
||||||
val borders: BorderColors
|
val borders: BorderColors
|
||||||
|
|
||||||
val outlines: OutlineColors
|
val outlines: OutlineColors
|
||||||
|
|
||||||
@SwingLafKey("*.infoForeground")
|
|
||||||
val infoContent: Color
|
val infoContent: Color
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
interface BorderColors {
|
interface BorderColors {
|
||||||
|
|
||||||
@SwingLafKey("Component.borderColor")
|
|
||||||
val normal: Color
|
val normal: Color
|
||||||
|
|
||||||
@SwingLafKey("Component.focusedBorderColor")
|
|
||||||
val focused: Color
|
val focused: Color
|
||||||
|
|
||||||
@SwingLafKey("*.disabledBorderColor")
|
|
||||||
val disabled: Color
|
val disabled: Color
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
interface OutlineColors {
|
interface OutlineColors {
|
||||||
|
|
||||||
@SwingLafKey("*.focusColor")
|
|
||||||
val focused: Color
|
val focused: Color
|
||||||
|
|
||||||
@SwingLafKey("Component.warningFocusColor")
|
|
||||||
val focusedWarning: Color
|
val focusedWarning: Color
|
||||||
|
|
||||||
@SwingLafKey("Component.errorFocusColor")
|
|
||||||
val focusedError: Color
|
val focusedError: Color
|
||||||
|
|
||||||
@SwingLafKey("Component.inactiveWarningFocusColor")
|
|
||||||
val warning: Color
|
val warning: Color
|
||||||
|
|
||||||
@SwingLafKey("Component.inactiveErrorFocusColor")
|
|
||||||
val error: Color
|
val error: Color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.jetbrains.jewel
|
package org.jetbrains.jewel
|
||||||
|
|
||||||
import androidx.compose.foundation.shape.CornerSize
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
@@ -9,7 +8,6 @@ import androidx.compose.ui.unit.Dp
|
|||||||
interface GlobalMetrics {
|
interface GlobalMetrics {
|
||||||
|
|
||||||
val outlineWidth: Dp
|
val outlineWidth: Dp
|
||||||
val outlineCornerSize: CornerSize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val LocalGlobalMetrics = staticCompositionLocalOf<GlobalMetrics> {
|
val LocalGlobalMetrics = staticCompositionLocalOf<GlobalMetrics> {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ fun GroupHeader(
|
|||||||
style: GroupHeaderStyle = LocalGroupHeaderStyle.current,
|
style: GroupHeaderStyle = LocalGroupHeaderStyle.current,
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalContentColor provides style.colors.content
|
LocalContentColor provides style.colors.content,
|
||||||
) {
|
) {
|
||||||
Row(modifier, verticalAlignment = Alignment.CenterVertically) {
|
Row(modifier, verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(text)
|
Text(text)
|
||||||
@@ -23,7 +23,7 @@ fun GroupHeader(
|
|||||||
color = style.colors.divider,
|
color = style.colors.divider,
|
||||||
orientation = Orientation.Horizontal,
|
orientation = Orientation.Horizontal,
|
||||||
startIndent = style.metrics.indent,
|
startIndent = style.metrics.indent,
|
||||||
thickness = style.metrics.dividerThickness
|
thickness = style.metrics.dividerThickness,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.paint
|
import androidx.compose.ui.draw.paint
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.isSpecified
|
||||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.graphics.toolingGraphicsLayer
|
import androidx.compose.ui.graphics.toolingGraphicsLayer
|
||||||
@@ -32,7 +32,6 @@ import androidx.compose.ui.semantics.semantics
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import org.xml.sax.InputSource
|
import org.xml.sax.InputSource
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.jar.JarFile
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Icon component that draws [imageVector] using [tint], defaulting to
|
* Icon component that draws [imageVector] using [tint], defaulting to
|
||||||
@@ -58,7 +57,7 @@ fun Icon(
|
|||||||
painter = rememberVectorPainter(imageVector),
|
painter = rememberVectorPainter(imageVector),
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
tint = tint
|
tint = tint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +86,7 @@ fun Icon(
|
|||||||
painter = painter,
|
painter = painter,
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
tint = tint
|
tint = tint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,15 +94,15 @@ fun Icon(
|
|||||||
fun Icon(
|
fun Icon(
|
||||||
resource: String,
|
resource: String,
|
||||||
contentDescription: String?,
|
contentDescription: String?,
|
||||||
resourceLoader: ResourceLoader = LocalResourceLoader.current,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
resourceLoader: ResourceLoader = LocalResourceLoader.current,
|
||||||
tint: Color = Color.Unspecified,
|
tint: Color = Color.Unspecified,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(resource, resourceLoader),
|
painter = painterResource(resource, resourceLoader),
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
tint = tint
|
tint = tint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,11 +122,32 @@ fun Icon(
|
|||||||
@Composable
|
@Composable
|
||||||
fun Icon(
|
fun Icon(
|
||||||
painter: Painter,
|
painter: Painter,
|
||||||
contentDescription: String? = null,
|
contentDescription: String?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
tint: Color = Color.Unspecified,
|
tint: Color = Color.Unspecified,
|
||||||
) {
|
) {
|
||||||
val colorFilter = if (tint == Color.Unspecified) null else ColorFilter.tint(tint)
|
val colorFilter = if (tint.isSpecified) ColorFilter.tint(tint) else null
|
||||||
|
Icon(painter, contentDescription, colorFilter, modifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon component that draws a [painter] using a [colorFilter]
|
||||||
|
*
|
||||||
|
* @param painter [Painter] to draw inside this Icon
|
||||||
|
* @param contentDescription text used by accessibility services to
|
||||||
|
* describe what this icon represents. This should always be
|
||||||
|
* provided unless this icon is used for decorative purposes, and
|
||||||
|
* does not represent a meaningful action that a user can take.
|
||||||
|
* @param modifier optional [Modifier] for this Icon
|
||||||
|
* @param colorFilter color filter to be applied to [painter]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun Icon(
|
||||||
|
painter: Painter,
|
||||||
|
contentDescription: String?,
|
||||||
|
colorFilter: ColorFilter?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
val semantics = if (contentDescription != null) {
|
val semantics = if (contentDescription != null) {
|
||||||
Modifier.semantics {
|
Modifier.semantics {
|
||||||
this.contentDescription = contentDescription
|
this.contentDescription = contentDescription
|
||||||
@@ -142,9 +162,9 @@ fun Icon(
|
|||||||
.paint(
|
.paint(
|
||||||
painter,
|
painter,
|
||||||
colorFilter = colorFilter,
|
colorFilter = colorFilter,
|
||||||
contentScale = ContentScale.Fit
|
contentScale = ContentScale.Fit,
|
||||||
)
|
)
|
||||||
.then(semantics)
|
.then(semantics),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,47 +222,16 @@ private inline fun <T> useResource(
|
|||||||
block: (InputStream) -> T,
|
block: (InputStream) -> T,
|
||||||
): T = loader.load(resourcePath).use(block)
|
): T = loader.load(resourcePath).use(block)
|
||||||
|
|
||||||
val LocalResourceLoader = staticCompositionLocalOf<ResourceLoader> {
|
|
||||||
ResourceLoader.Default
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Modifier.defaultSizeFor(painter: Painter) =
|
private fun Modifier.defaultSizeFor(painter: Painter) =
|
||||||
this.then(
|
then(
|
||||||
if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isInfinite()) {
|
if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isInfinite()) {
|
||||||
DefaultIconSizeModifier
|
DefaultIconSizeModifier
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun Size.isInfinite() = width.isInfinite() && height.isInfinite()
|
private fun Size.isInfinite() = width.isInfinite() && height.isInfinite()
|
||||||
|
|
||||||
// Default icon size, for icons with no intrinsic size information
|
// Default icon size, for icons with no intrinsic size information
|
||||||
private val DefaultIconSizeModifier = Modifier.size(16.dp)
|
private val DefaultIconSizeModifier = Modifier.size(16.dp)
|
||||||
|
|
||||||
fun getJarPath(klass: Class<*>): String? {
|
|
||||||
val className = klass.name.replace('.', '/') + ".class"
|
|
||||||
val classPath = klass.classLoader.getResource(className)?.toString() ?: return null
|
|
||||||
if (!classPath.startsWith("jar")) {
|
|
||||||
// Class not from a JAR
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return classPath.substringBefore("!").removePrefix("jar:file:")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun extractFileFromJar(jarPath: String, filePath: String) =
|
|
||||||
JarFile(jarPath).use { jar ->
|
|
||||||
jar.getEntry(filePath)?.let { entry ->
|
|
||||||
jar.getInputStream(entry).use { it.readBytes().inputStream() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T> getJarPath(): String? = getJarPath(T::class.java)
|
|
||||||
|
|
||||||
class RawJarResourceLoader(private val jars: List<String>) : ResourceLoader {
|
|
||||||
|
|
||||||
override fun load(resourcePath: String): InputStream =
|
|
||||||
jars.mapNotNull { jarPath ->
|
|
||||||
extractFileFromJar(jarPath, resourcePath)
|
|
||||||
}.firstOrNull() ?: error("Resource $resourcePath not found in jars $jars")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.graphics.isSpecified
|
import androidx.compose.ui.graphics.isSpecified
|
||||||
import androidx.compose.ui.text.TextLayoutResult
|
import androidx.compose.ui.text.TextLayoutResult
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
@@ -33,7 +34,7 @@ internal fun InputField(
|
|||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
readOnly: Boolean,
|
readOnly: Boolean,
|
||||||
isError: Boolean,
|
outline: Outline,
|
||||||
undecorated: Boolean,
|
undecorated: Boolean,
|
||||||
visualTransformation: VisualTransformation,
|
visualTransformation: VisualTransformation,
|
||||||
keyboardOptions: KeyboardOptions,
|
keyboardOptions: KeyboardOptions,
|
||||||
@@ -47,16 +48,15 @@ internal fun InputField(
|
|||||||
decorationBox: @Composable (innerTextField: @Composable () -> Unit, state: InputFieldState) -> Unit,
|
decorationBox: @Composable (innerTextField: @Composable () -> Unit, state: InputFieldState) -> Unit,
|
||||||
) {
|
) {
|
||||||
var inputState by remember(interactionSource) {
|
var inputState by remember(interactionSource) {
|
||||||
mutableStateOf(InputFieldState.of(enabled = enabled, error = isError))
|
mutableStateOf(InputFieldState.of(enabled = enabled))
|
||||||
}
|
}
|
||||||
remember(isError, enabled) {
|
remember(enabled) {
|
||||||
inputState = inputState.copy(error = isError, enabled = enabled)
|
inputState = inputState.copy(enabled = enabled)
|
||||||
}
|
}
|
||||||
LaunchedEffect(interactionSource) {
|
LaunchedEffect(interactionSource) {
|
||||||
interactionSource.interactions.collect { interaction ->
|
interactionSource.interactions.collect { interaction ->
|
||||||
when (interaction) {
|
when (interaction) {
|
||||||
is FocusInteraction.Focus -> inputState = inputState.copy(focused = true)
|
is FocusInteraction.Focus -> inputState = inputState.copy(focused = true)
|
||||||
|
|
||||||
is FocusInteraction.Unfocus -> inputState = inputState.copy(focused = false)
|
is FocusInteraction.Unfocus -> inputState = inputState.copy(focused = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,26 +73,28 @@ internal fun InputField(
|
|||||||
val borderColor by style.colors.borderFor(inputState)
|
val borderColor by style.colors.borderFor(inputState)
|
||||||
val borderModifier = Modifier.appendIf(!undecorated && borderColor.isSpecified) {
|
val borderModifier = Modifier.appendIf(!undecorated && borderColor.isSpecified) {
|
||||||
Modifier.border(
|
Modifier.border(
|
||||||
alignment = Stroke.Alignment.Outside,
|
alignment = Stroke.Alignment.Center,
|
||||||
width = style.metrics.borderWidth,
|
width = style.metrics.borderWidth,
|
||||||
color = borderColor,
|
color = borderColor,
|
||||||
shape = shape
|
shape = shape,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val contentColor by colors.contentFor(inputState)
|
val contentColor by colors.contentFor(inputState)
|
||||||
val mergedTextStyle = style.textStyle.merge(textStyle).copy(color = contentColor)
|
val mergedTextStyle = style.textStyle.merge(textStyle).copy(color = contentColor)
|
||||||
val cursorBrush by colors.cursorFor(inputState)
|
val caretColor by colors.caretFor(inputState)
|
||||||
|
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = value,
|
value = value,
|
||||||
modifier = modifier.then(backgroundModifier)
|
modifier = modifier.then(backgroundModifier)
|
||||||
.then(borderModifier),
|
.then(borderModifier)
|
||||||
|
.focusOutline(inputState, shape)
|
||||||
|
.outline(inputState, outline, shape),
|
||||||
onValueChange = onValueChange,
|
onValueChange = onValueChange,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
textStyle = mergedTextStyle,
|
textStyle = mergedTextStyle,
|
||||||
cursorBrush = cursorBrush,
|
cursorBrush = SolidColor(caretColor),
|
||||||
visualTransformation = visualTransformation,
|
visualTransformation = visualTransformation,
|
||||||
onTextLayout = onTextLayout,
|
onTextLayout = onTextLayout,
|
||||||
keyboardOptions = keyboardOptions,
|
keyboardOptions = keyboardOptions,
|
||||||
@@ -102,13 +104,13 @@ internal fun InputField(
|
|||||||
maxLines = maxLines,
|
maxLines = maxLines,
|
||||||
decorationBox = @Composable { innerTextField: @Composable () -> Unit ->
|
decorationBox = @Composable { innerTextField: @Composable () -> Unit ->
|
||||||
decorationBox(innerTextField, inputState)
|
decorationBox(innerTextField, inputState)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class InputFieldState(val state: ULong) : StateWithOutline {
|
value class InputFieldState(val state: ULong) : FocusableComponentState {
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
override val isActive: Boolean
|
override val isActive: Boolean
|
||||||
@@ -122,14 +124,6 @@ value class InputFieldState(val state: ULong) : StateWithOutline {
|
|||||||
override val isFocused: Boolean
|
override val isFocused: Boolean
|
||||||
get() = state and CommonStateBitMask.Focused != 0UL
|
get() = state and CommonStateBitMask.Focused != 0UL
|
||||||
|
|
||||||
@Stable
|
|
||||||
override val isError: Boolean
|
|
||||||
get() = state and CommonStateBitMask.Error != 0UL
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
override val isWarning: Boolean
|
|
||||||
get() = state and CommonStateBitMask.Warning != 0UL
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
override val isHovered: Boolean
|
override val isHovered: Boolean
|
||||||
get() = state and CommonStateBitMask.Hovered != 0UL
|
get() = state and CommonStateBitMask.Hovered != 0UL
|
||||||
@@ -141,23 +135,19 @@ value class InputFieldState(val state: ULong) : StateWithOutline {
|
|||||||
fun copy(
|
fun copy(
|
||||||
enabled: Boolean = isEnabled,
|
enabled: Boolean = isEnabled,
|
||||||
focused: Boolean = isFocused,
|
focused: Boolean = isFocused,
|
||||||
error: Boolean = isError,
|
|
||||||
pressed: Boolean = isPressed,
|
pressed: Boolean = isPressed,
|
||||||
hovered: Boolean = isHovered,
|
hovered: Boolean = isHovered,
|
||||||
warning: Boolean = isWarning,
|
|
||||||
active: Boolean = isActive,
|
active: Boolean = isActive,
|
||||||
) = of(
|
) = of(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
focused = focused,
|
focused = focused,
|
||||||
error = error,
|
|
||||||
pressed = pressed,
|
pressed = pressed,
|
||||||
hovered = hovered,
|
hovered = hovered,
|
||||||
warning = warning,
|
active = active,
|
||||||
active = active
|
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun toString() =
|
override fun toString() =
|
||||||
"${javaClass.simpleName}(isEnabled=$isEnabled, isFocused=$isFocused, isError=$isError, isWarning=$isWarning, " +
|
"${javaClass.simpleName}(isEnabled=$isEnabled, isFocused=$isFocused, " +
|
||||||
"isHovered=$isHovered, isPressed=$isPressed, isActive=$isActive)"
|
"isHovered=$isHovered, isPressed=$isPressed, isActive=$isActive)"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -165,19 +155,15 @@ value class InputFieldState(val state: ULong) : StateWithOutline {
|
|||||||
fun of(
|
fun of(
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
focused: Boolean = false,
|
focused: Boolean = false,
|
||||||
error: Boolean = false,
|
|
||||||
pressed: Boolean = false,
|
pressed: Boolean = false,
|
||||||
hovered: Boolean = false,
|
hovered: Boolean = false,
|
||||||
warning: Boolean = false,
|
|
||||||
active: Boolean = false,
|
active: Boolean = false,
|
||||||
) = InputFieldState(
|
) = InputFieldState(
|
||||||
state = (if (enabled) CommonStateBitMask.Enabled else 0UL) or
|
state = (if (enabled) CommonStateBitMask.Enabled else 0UL) or
|
||||||
(if (focused) CommonStateBitMask.Focused else 0UL) or
|
(if (focused) CommonStateBitMask.Focused else 0UL) or
|
||||||
(if (error) CommonStateBitMask.Error else 0UL) or
|
|
||||||
(if (hovered) CommonStateBitMask.Hovered else 0UL) or
|
(if (hovered) CommonStateBitMask.Hovered else 0UL) or
|
||||||
(if (pressed) CommonStateBitMask.Pressed else 0UL) or
|
(if (pressed) CommonStateBitMask.Pressed else 0UL) or
|
||||||
(if (warning) CommonStateBitMask.Warning else 0UL) or
|
(if (active) CommonStateBitMask.Active else 0UL),
|
||||||
(if (active) CommonStateBitMask.Active else 0UL)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,23 +19,23 @@ import org.jetbrains.jewel.styling.TextFieldStyle
|
|||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class IntelliJComponentStyling(
|
class IntelliJComponentStyling(
|
||||||
val defaultButtonStyle: ButtonStyle,
|
|
||||||
val outlinedButtonStyle: ButtonStyle,
|
|
||||||
val checkboxStyle: CheckboxStyle,
|
val checkboxStyle: CheckboxStyle,
|
||||||
val chipStyle: ChipStyle,
|
val chipStyle: ChipStyle,
|
||||||
|
val defaultButtonStyle: ButtonStyle,
|
||||||
|
val defaultTabStyle: TabStyle,
|
||||||
val dropdownStyle: DropdownStyle,
|
val dropdownStyle: DropdownStyle,
|
||||||
|
val editorTabStyle: TabStyle,
|
||||||
val groupHeaderStyle: GroupHeaderStyle,
|
val groupHeaderStyle: GroupHeaderStyle,
|
||||||
|
val horizontalProgressBarStyle: HorizontalProgressBarStyle,
|
||||||
val labelledTextFieldStyle: LabelledTextFieldStyle,
|
val labelledTextFieldStyle: LabelledTextFieldStyle,
|
||||||
|
val lazyTreeStyle: LazyTreeStyle,
|
||||||
val linkStyle: LinkStyle,
|
val linkStyle: LinkStyle,
|
||||||
val menuStyle: MenuStyle,
|
val menuStyle: MenuStyle,
|
||||||
val horizontalProgressBarStyle: HorizontalProgressBarStyle,
|
val outlinedButtonStyle: ButtonStyle,
|
||||||
val radioButtonStyle: RadioButtonStyle,
|
val radioButtonStyle: RadioButtonStyle,
|
||||||
val scrollbarStyle: ScrollbarStyle,
|
val scrollbarStyle: ScrollbarStyle,
|
||||||
val textAreaStyle: TextAreaStyle,
|
val textAreaStyle: TextAreaStyle,
|
||||||
val textFieldStyle: TextFieldStyle,
|
val textFieldStyle: TextFieldStyle,
|
||||||
val lazyTreeStyle: LazyTreeStyle,
|
|
||||||
val defaultTabStyle: TabStyle,
|
|
||||||
val editorTabStyle: TabStyle,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -85,4 +85,13 @@ class IntelliJComponentStyling(
|
|||||||
result = 31 * result + editorTabStyle.hashCode()
|
result = 31 * result + editorTabStyle.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String =
|
||||||
|
"IntelliJComponentStyling(checkboxStyle=$checkboxStyle, chipStyle=$chipStyle, " +
|
||||||
|
"defaultButtonStyle=$defaultButtonStyle, defaultTabStyle=$defaultTabStyle, dropdownStyle=$dropdownStyle, " +
|
||||||
|
"editorTabStyle=$editorTabStyle, groupHeaderStyle=$groupHeaderStyle, " +
|
||||||
|
"horizontalProgressBarStyle=$horizontalProgressBarStyle, labelledTextFieldStyle=$labelledTextFieldStyle, " +
|
||||||
|
"lazyTreeStyle=$lazyTreeStyle, linkStyle=$linkStyle, menuStyle=$menuStyle, " +
|
||||||
|
"outlinedButtonStyle=$outlinedButtonStyle, radioButtonStyle=$radioButtonStyle, " +
|
||||||
|
"scrollbarStyle=$scrollbarStyle, textAreaStyle=$textAreaStyle, textFieldStyle=$textFieldStyle)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.jetbrains.jewel.themes.intui.core
|
package org.jetbrains.jewel
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
@@ -7,8 +7,6 @@ import androidx.compose.ui.graphics.painter.Painter
|
|||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.ResourceLoader
|
import androidx.compose.ui.res.ResourceLoader
|
||||||
import androidx.compose.ui.res.loadSvgPainter
|
import androidx.compose.ui.res.loadSvgPainter
|
||||||
import org.jetbrains.jewel.SvgLoader
|
|
||||||
import org.jetbrains.jewel.SvgPatcher
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
@@ -39,22 +37,23 @@ class IntelliJSvgLoader(private val svgPatcher: SvgPatcher) : SvgLoader {
|
|||||||
): Painter {
|
): Painter {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
val painter = try {
|
val painter =
|
||||||
useResource(resourcePath, loader) {
|
try {
|
||||||
loadSvgPainter(it.patchColors(), density)
|
useResource(resourcePath, loader) {
|
||||||
|
loadSvgPainter(it.patchColors(), density)
|
||||||
|
}
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
val simplerPath = trySimplifyingPath(resourcePath)
|
||||||
|
if (simplerPath != null) {
|
||||||
|
System.err.println("Unable to load '$resourcePath' (base: $basePath), trying simpler version: $simplerPath")
|
||||||
|
return rememberPatchedSvgResource(basePath, simplerPath, loader)
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Unable to load '$resourcePath' (base: $basePath), no simpler version available",
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
val simplerPath = trySimplifyingPath(resourcePath)
|
|
||||||
if (simplerPath != null) {
|
|
||||||
System.err.println("Unable to load '$resourcePath' (base: $basePath), trying simpler version: $simplerPath")
|
|
||||||
rememberPatchedSvgResource(basePath, simplerPath, loader)
|
|
||||||
} else {
|
|
||||||
throw IllegalArgumentException(
|
|
||||||
"Unable to load '$resourcePath' (base: $basePath), no simpler version available",
|
|
||||||
e
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return remember(resourcePath, density, loader) { painter }
|
return remember(resourcePath, density, loader) { painter }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import org.jetbrains.jewel.styling.ButtonStyle
|
import org.jetbrains.jewel.styling.ButtonStyle
|
||||||
import org.jetbrains.jewel.styling.CheckboxStyle
|
import org.jetbrains.jewel.styling.CheckboxStyle
|
||||||
@@ -61,6 +62,11 @@ interface IntelliJTheme {
|
|||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
get() = LocalTextStyle.current
|
get() = LocalTextStyle.current
|
||||||
|
|
||||||
|
val contentColor: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalContentColor.current
|
||||||
|
|
||||||
val isDark: Boolean
|
val isDark: Boolean
|
||||||
@Composable
|
@Composable
|
||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
@@ -172,7 +178,6 @@ interface IntelliJTheme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExperimentalJewelApi
|
|
||||||
@Composable
|
@Composable
|
||||||
fun IntelliJTheme(
|
fun IntelliJTheme(
|
||||||
theme: IntelliJThemeDefinition,
|
theme: IntelliJThemeDefinition,
|
||||||
@@ -186,13 +191,15 @@ fun IntelliJTheme(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun IntelliJTheme(theme: IntelliJThemeDefinition, content: @Composable () -> Unit) {
|
fun IntelliJTheme(theme: IntelliJThemeDefinition, content: @Composable () -> Unit) {
|
||||||
|
val defaultTextStyle = theme.defaultTextStyle
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalContentColor provides theme.defaultTextStyle.color,
|
|
||||||
LocalIsDarkTheme provides theme.isDark,
|
LocalIsDarkTheme provides theme.isDark,
|
||||||
LocalTextStyle provides theme.defaultTextStyle,
|
LocalContentColor provides defaultTextStyle.color,
|
||||||
|
LocalTextStyle provides defaultTextStyle,
|
||||||
LocalGlobalColors provides theme.globalColors,
|
LocalGlobalColors provides theme.globalColors,
|
||||||
LocalGlobalMetrics provides theme.metrics,
|
LocalGlobalMetrics provides theme.globalMetrics,
|
||||||
content = content
|
content = content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package org.jetbrains.jewel
|
package org.jetbrains.jewel
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
@Stable
|
||||||
interface IntelliJThemeColorPalette {
|
interface IntelliJThemeColorPalette {
|
||||||
|
|
||||||
fun lookup(colorKey: String): Color?
|
fun lookup(colorKey: String): Color?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
object EmptyThemeColorPalette : IntelliJThemeColorPalette {
|
object EmptyThemeColorPalette : IntelliJThemeColorPalette {
|
||||||
|
|
||||||
override fun lookup(colorKey: String): Color? = null
|
override fun lookup(colorKey: String): Color? = null
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ interface IntelliJThemeDefinition {
|
|||||||
|
|
||||||
val isDark: Boolean
|
val isDark: Boolean
|
||||||
val globalColors: GlobalColors
|
val globalColors: GlobalColors
|
||||||
val metrics: GlobalMetrics
|
val globalMetrics: GlobalMetrics
|
||||||
val defaultTextStyle: TextStyle
|
val defaultTextStyle: TextStyle
|
||||||
|
|
||||||
val colorPalette: IntelliJThemeColorPalette
|
val colorPalette: IntelliJThemeColorPalette
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package org.jetbrains.jewel
|
package org.jetbrains.jewel
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
interface IntelliJThemeIconData {
|
interface IntelliJThemeIconData {
|
||||||
|
|
||||||
val iconOverrides: Map<String, String>
|
val iconOverrides: Map<String, String>
|
||||||
val colorPalette: Map<String, String>
|
val colorPalette: Map<String, String>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
object EmptyThemeIconData : IntelliJThemeIconData {
|
object EmptyThemeIconData : IntelliJThemeIconData {
|
||||||
|
|
||||||
override val iconOverrides: Map<String, String> = emptyMap()
|
override val iconOverrides: Map<String, String> = emptyMap()
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package org.jetbrains.jewel
|
||||||
|
|
||||||
|
interface JewelResourceLoader {
|
||||||
|
|
||||||
|
val searchClasses: List<Class<out Any>>
|
||||||
|
}
|
||||||
@@ -21,16 +21,23 @@ import androidx.compose.ui.text.input.VisualTransformation
|
|||||||
import androidx.compose.ui.unit.offset
|
import androidx.compose.ui.unit.offset
|
||||||
import org.jetbrains.jewel.styling.LabelledTextFieldStyle
|
import org.jetbrains.jewel.styling.LabelledTextFieldStyle
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param placeholder the optional placeholder to be displayed over the component when
|
||||||
|
* the [value] is empty.
|
||||||
|
* @param hint the optional hint to be displayed underneath the component. By default it
|
||||||
|
* will have a greyed out appearance and smaller text.
|
||||||
|
* @param label the label to display above the component.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun LabelledTextField(
|
fun LabelledTextField(
|
||||||
label: @Composable () -> Unit,
|
|
||||||
value: String,
|
value: String,
|
||||||
onValueChange: (String) -> Unit,
|
onValueChange: (String) -> Unit,
|
||||||
|
label: @Composable () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
textFieldModifier: Modifier = Modifier,
|
textFieldModifier: Modifier = Modifier,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
isError: Boolean = false,
|
outline: Outline = Outline.None,
|
||||||
hint: @Composable (() -> Unit)? = null,
|
hint: @Composable (() -> Unit)? = null,
|
||||||
placeholder: @Composable (() -> Unit)? = null,
|
placeholder: @Composable (() -> Unit)? = null,
|
||||||
trailingIcon: @Composable (() -> Unit)? = null,
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
@@ -61,7 +68,7 @@ fun LabelledTextField(
|
|||||||
textFieldModifier = textFieldModifier,
|
textFieldModifier = textFieldModifier,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
isError = isError,
|
outline = outline,
|
||||||
hint = hint,
|
hint = hint,
|
||||||
placeholder = placeholder,
|
placeholder = placeholder,
|
||||||
trailingIcon = trailingIcon,
|
trailingIcon = trailingIcon,
|
||||||
@@ -72,10 +79,17 @@ fun LabelledTextField(
|
|||||||
onTextLayout = onTextLayout,
|
onTextLayout = onTextLayout,
|
||||||
style = style,
|
style = style,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
interactionSource = interactionSource
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param placeholder the optional placeholder to be displayed over the component when
|
||||||
|
* the [value] is empty.
|
||||||
|
* @param hint the optional hint to be displayed underneath the component. By default it
|
||||||
|
* will have a greyed out appearance and smaller text.
|
||||||
|
* @param label the label to display above the component.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun LabelledTextField(
|
fun LabelledTextField(
|
||||||
label: @Composable () -> Unit,
|
label: @Composable () -> Unit,
|
||||||
@@ -85,7 +99,7 @@ fun LabelledTextField(
|
|||||||
textFieldModifier: Modifier = Modifier,
|
textFieldModifier: Modifier = Modifier,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
isError: Boolean = false,
|
outline: Outline = Outline.None,
|
||||||
hint: @Composable (() -> Unit)? = null,
|
hint: @Composable (() -> Unit)? = null,
|
||||||
placeholder: @Composable (() -> Unit)? = null,
|
placeholder: @Composable (() -> Unit)? = null,
|
||||||
trailingIcon: @Composable (() -> Unit)? = null,
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
@@ -104,7 +118,7 @@ fun LabelledTextField(
|
|||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalTextStyle provides style.textStyles.label,
|
LocalTextStyle provides style.textStyles.label,
|
||||||
LocalContentColor provides style.colors.label,
|
LocalContentColor provides style.colors.label,
|
||||||
content = label
|
content = label,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
textField = {
|
textField = {
|
||||||
@@ -114,7 +128,7 @@ fun LabelledTextField(
|
|||||||
modifier = textFieldModifier,
|
modifier = textFieldModifier,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
isError = isError,
|
outline = outline,
|
||||||
placeholder = placeholder,
|
placeholder = placeholder,
|
||||||
trailingIcon = trailingIcon,
|
trailingIcon = trailingIcon,
|
||||||
undecorated = undecorated,
|
undecorated = undecorated,
|
||||||
@@ -124,7 +138,7 @@ fun LabelledTextField(
|
|||||||
onTextLayout = onTextLayout,
|
onTextLayout = onTextLayout,
|
||||||
style = style,
|
style = style,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
interactionSource = interactionSource
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
hint = hint?.let {
|
hint = hint?.let {
|
||||||
@@ -132,11 +146,11 @@ fun LabelledTextField(
|
|||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalTextStyle provides style.textStyles.hint,
|
LocalTextStyle provides style.textStyles.hint,
|
||||||
LocalContentColor provides style.colors.hint,
|
LocalContentColor provides style.colors.hint,
|
||||||
content = it
|
content = it,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style = style
|
style = style,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +178,7 @@ private fun LabelledTextFieldLayout(
|
|||||||
hint()
|
hint()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
) { measurables, incomingConstraints ->
|
) { measurables, incomingConstraints ->
|
||||||
val hintMeasurable = measurables.firstOrNull { it.layoutId == HINT_ID }
|
val hintMeasurable = measurables.firstOrNull { it.layoutId == HINT_ID }
|
||||||
|
|
||||||
@@ -173,7 +187,7 @@ private fun LabelledTextFieldLayout(
|
|||||||
|
|
||||||
val constraintsWithoutSpacing = incomingConstraints.offset(
|
val constraintsWithoutSpacing = incomingConstraints.offset(
|
||||||
horizontal = -horizontalSpacing,
|
horizontal = -horizontalSpacing,
|
||||||
vertical = -verticalSpacing
|
vertical = -verticalSpacing,
|
||||||
)
|
)
|
||||||
|
|
||||||
val textFieldPlaceable = measurables.first { it.layoutId == TEXT_FIELD_ID }
|
val textFieldPlaceable = measurables.first { it.layoutId == TEXT_FIELD_ID }
|
||||||
@@ -185,7 +199,7 @@ private fun LabelledTextFieldLayout(
|
|||||||
val hintPlaceable = hintMeasurable?.measure(
|
val hintPlaceable = hintMeasurable?.measure(
|
||||||
constraintsWithoutSpacing
|
constraintsWithoutSpacing
|
||||||
.offset(vertical = -textFieldPlaceable.height)
|
.offset(vertical = -textFieldPlaceable.height)
|
||||||
.copy(maxWidth = textFieldPlaceable.width)
|
.copy(maxWidth = textFieldPlaceable.width),
|
||||||
)
|
)
|
||||||
|
|
||||||
val width = labelPlaceable.width + textFieldPlaceable.width + horizontalSpacing
|
val width = labelPlaceable.width + textFieldPlaceable.width + horizontalSpacing
|
||||||
@@ -194,15 +208,15 @@ private fun LabelledTextFieldLayout(
|
|||||||
layout(width, height) {
|
layout(width, height) {
|
||||||
labelPlaceable.placeRelative(
|
labelPlaceable.placeRelative(
|
||||||
x = 0,
|
x = 0,
|
||||||
y = Alignment.CenterVertically.align(labelPlaceable.height, textFieldPlaceable.height)
|
y = Alignment.CenterVertically.align(labelPlaceable.height, textFieldPlaceable.height),
|
||||||
)
|
)
|
||||||
textFieldPlaceable.placeRelative(
|
textFieldPlaceable.placeRelative(
|
||||||
x = labelPlaceable.width + horizontalSpacing,
|
x = labelPlaceable.width + horizontalSpacing,
|
||||||
y = 0
|
y = 0,
|
||||||
)
|
)
|
||||||
hintPlaceable?.placeRelative(
|
hintPlaceable?.placeRelative(
|
||||||
x = labelPlaceable.width + horizontalSpacing,
|
x = labelPlaceable.width + horizontalSpacing,
|
||||||
y = textFieldPlaceable.height + verticalSpacing
|
y = textFieldPlaceable.height + verticalSpacing,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package org.jetbrains.jewel
|
package org.jetbrains.jewel
|
||||||
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.rotate
|
|
||||||
import androidx.compose.ui.graphics.takeOrElse
|
import androidx.compose.ui.graphics.takeOrElse
|
||||||
import androidx.compose.ui.res.ResourceLoader
|
import androidx.compose.ui.res.ResourceLoader
|
||||||
import org.jetbrains.jewel.foundation.lazy.SelectableLazyItemScope
|
import org.jetbrains.jewel.foundation.lazy.SelectableLazyItemScope
|
||||||
@@ -52,14 +51,10 @@ fun <T> LazyTree(
|
|||||||
onElementDoubleClick = onElementDoubleClick,
|
onElementDoubleClick = onElementDoubleClick,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
keyActions = keyActions,
|
keyActions = keyActions,
|
||||||
chevronContent = { state ->
|
chevronContent = { elementState ->
|
||||||
Box(Modifier.rotate(if (state.isExpanded) 90f else 0f)) {
|
val painterProvider = style.icons.nodeChevron(elementState.isExpanded)
|
||||||
Icon(
|
val painter by painterProvider.getPainter(elementState, resourceLoader)
|
||||||
painter = painterResource(style.icons.nodeChevron, resourceLoader),
|
Icon(painter = painter, contentDescription = null)
|
||||||
contentDescription = "Dropdown link",
|
|
||||||
tint = colors.chevronTintFor(state).value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
nodeContent = {
|
nodeContent = {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
@@ -68,12 +63,12 @@ fun <T> LazyTree(
|
|||||||
TreeElementState.of(
|
TreeElementState.of(
|
||||||
isFocused,
|
isFocused,
|
||||||
isSelected,
|
isSelected,
|
||||||
false
|
false,
|
||||||
)
|
),
|
||||||
).value
|
).value
|
||||||
.takeOrElse { LocalContentColor.current }
|
.takeOrElse { LocalContentColor.current }
|
||||||
)
|
),
|
||||||
) { nodeContent(it) }
|
) { nodeContent(it) }
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import androidx.compose.ui.unit.Density
|
|||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import org.jetbrains.jewel.styling.HorizontalProgressBarStyle
|
import org.jetbrains.jewel.styling.HorizontalProgressBarStyle
|
||||||
|
|
||||||
|
// TODO implement green/red/yellow variants based on com.intellij.openapi.progress.util.ColorProgressBar
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HorizontalProgressBar(
|
fun HorizontalProgressBar(
|
||||||
progress: Float, // from 0 to 1
|
progress: Float, // from 0 to 1
|
||||||
@@ -48,9 +50,9 @@ fun HorizontalProgressBar(
|
|||||||
color = colors.progress,
|
color = colors.progress,
|
||||||
topLeft = Offset(progressX, 0f),
|
topLeft = Offset(progressX, 0f),
|
||||||
size = size.copy(width = progressWidth),
|
size = size.copy(width = progressWidth),
|
||||||
cornerRadius = cornerRadius
|
cornerRadius = cornerRadius,
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,13 +69,13 @@ fun IndeterminateHorizontalProgressBar(
|
|||||||
targetValue = 2f,
|
targetValue = 2f,
|
||||||
animationSpec = infiniteRepeatable(
|
animationSpec = infiniteRepeatable(
|
||||||
tween(durationMillis = cycleDurationMillis, easing = LinearEasing),
|
tween(durationMillis = cycleDurationMillis, easing = LinearEasing),
|
||||||
repeatMode = RepeatMode.Restart
|
repeatMode = RepeatMode.Restart,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val highlightWidth = style.metrics.indeterminateHighlightWidth
|
val highlightWidth = style.metrics.indeterminateHighlightWidth
|
||||||
val colors = style.colors
|
val colors = style.colors
|
||||||
val colorsList by remember { mutableStateOf(listOf(colors.progress, colors.indeterminateHighlight)) }
|
val colorsList by remember { mutableStateOf(listOf(colors.indeterminateBase, colors.indeterminateHighlight)) }
|
||||||
val shape = RoundedCornerShape(style.metrics.cornerSize)
|
val shape = RoundedCornerShape(style.metrics.cornerSize)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
@@ -90,9 +92,9 @@ fun IndeterminateHorizontalProgressBar(
|
|||||||
colors = colorsList,
|
colors = colorsList,
|
||||||
start = Offset(x, 0f),
|
start = Offset(x, 0f),
|
||||||
end = Offset(x + highlightWidth.value, 0f),
|
end = Offset(x + highlightWidth.value, 0f),
|
||||||
TileMode.Mirror
|
TileMode.Mirror,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.input.InputMode
|
import androidx.compose.ui.input.InputMode
|
||||||
import androidx.compose.ui.platform.LocalInputModeManager
|
import androidx.compose.ui.platform.LocalInputModeManager
|
||||||
import androidx.compose.ui.res.ResourceLoader
|
import androidx.compose.ui.res.ResourceLoader
|
||||||
@@ -85,7 +86,7 @@ fun Link(
|
|||||||
indication = indication,
|
indication = indication,
|
||||||
style = style,
|
style = style,
|
||||||
resourceLoader = resourceLoader,
|
resourceLoader = resourceLoader,
|
||||||
icon = null
|
icon = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +126,7 @@ fun ExternalLink(
|
|||||||
indication = indication,
|
indication = indication,
|
||||||
style = style,
|
style = style,
|
||||||
resourceLoader = resourceLoader,
|
resourceLoader = resourceLoader,
|
||||||
icon = style.icons.externalLink
|
icon = style.icons.externalLink,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ fun DropdownLink(
|
|||||||
Box(
|
Box(
|
||||||
Modifier.onHover {
|
Modifier.onHover {
|
||||||
hovered = it
|
hovered = it
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
LinkImpl(
|
LinkImpl(
|
||||||
text = text,
|
text = text,
|
||||||
@@ -180,7 +181,7 @@ fun DropdownLink(
|
|||||||
indication = indication,
|
indication = indication,
|
||||||
style = style,
|
style = style,
|
||||||
icon = style.icons.dropdownChevron,
|
icon = style.icons.dropdownChevron,
|
||||||
resourceLoader = resourceLoader
|
resourceLoader = resourceLoader,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
@@ -195,7 +196,8 @@ fun DropdownLink(
|
|||||||
modifier = menuModifier,
|
modifier = menuModifier,
|
||||||
style = menuStyle,
|
style = menuStyle,
|
||||||
horizontalAlignment = Alignment.Start, // TODO no idea what goes here
|
horizontalAlignment = Alignment.Start, // TODO no idea what goes here
|
||||||
content = menuContent
|
content = menuContent,
|
||||||
|
resourceLoader = resourceLoader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,8 +264,8 @@ private fun LinkImpl(
|
|||||||
lineHeight = lineHeight,
|
lineHeight = lineHeight,
|
||||||
fontFamily = fontFamily,
|
fontFamily = fontFamily,
|
||||||
fontStyle = fontStyle,
|
fontStyle = fontStyle,
|
||||||
letterSpacing = letterSpacing
|
letterSpacing = letterSpacing,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val clickable = Modifier.clickable(
|
val clickable = Modifier.clickable(
|
||||||
@@ -274,28 +276,28 @@ private fun LinkImpl(
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
indication = indication
|
indication = indication,
|
||||||
)
|
)
|
||||||
|
|
||||||
val focusHaloModifier = Modifier.border(
|
val focusHaloModifier = Modifier.border(
|
||||||
alignment = Stroke.Alignment.Outside,
|
alignment = Stroke.Alignment.Outside,
|
||||||
width = LocalGlobalMetrics.current.outlineWidth,
|
width = LocalGlobalMetrics.current.outlineWidth,
|
||||||
color = LocalGlobalColors.current.outlines.focused,
|
color = LocalGlobalColors.current.outlines.focused,
|
||||||
shape = RoundedCornerShape(style.metrics.focusHaloCornerSize)
|
shape = RoundedCornerShape(style.metrics.focusHaloCornerSize),
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.then(clickable)
|
modifier = modifier.then(clickable)
|
||||||
.appendIf(linkState.isFocused) { focusHaloModifier },
|
.appendIf(linkState.isFocused) { focusHaloModifier },
|
||||||
horizontalArrangement = Arrangement.spacedBy(style.metrics.textIconGap),
|
horizontalArrangement = Arrangement.spacedBy(style.metrics.textIconGap),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
BasicText(
|
BasicText(
|
||||||
text = text,
|
text = text,
|
||||||
style = mergedStyle,
|
style = mergedStyle,
|
||||||
overflow = overflow,
|
overflow = overflow,
|
||||||
softWrap = true,
|
softWrap = true,
|
||||||
maxLines = 1
|
maxLines = 1,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (icon != null) {
|
if (icon != null) {
|
||||||
@@ -304,7 +306,7 @@ private fun LinkImpl(
|
|||||||
iconPainter,
|
iconPainter,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(style.metrics.iconSize),
|
modifier = Modifier.size(style.metrics.iconSize),
|
||||||
tint = style.colors.iconTint
|
colorFilter = if (!linkState.isEnabled) ColorFilter.disabled() else null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,7 +357,7 @@ value class LinkState(val state: ULong) : FocusableComponentState {
|
|||||||
visited = visited,
|
visited = visited,
|
||||||
pressed = pressed,
|
pressed = pressed,
|
||||||
hovered = hovered,
|
hovered = hovered,
|
||||||
active = active
|
active = active,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -397,7 +399,7 @@ value class LinkState(val state: ULong) : FocusableComponentState {
|
|||||||
(if (focused) Focused else 0UL) or
|
(if (focused) Focused else 0UL) or
|
||||||
(if (pressed) Pressed else 0UL) or
|
(if (pressed) Pressed else 0UL) or
|
||||||
(if (hovered) Hovered else 0UL) or
|
(if (hovered) Hovered else 0UL) or
|
||||||
(if (active) Active else 0UL)
|
(if (active) Active else 0UL),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ import androidx.compose.foundation.interaction.PressInteraction
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.rememberScrollbarAdapter
|
import androidx.compose.foundation.rememberScrollbarAdapter
|
||||||
@@ -31,13 +33,17 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.focus.FocusManager
|
import androidx.compose.ui.focus.FocusManager
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.InputMode
|
import androidx.compose.ui.input.InputMode
|
||||||
import androidx.compose.ui.input.InputModeManager
|
import androidx.compose.ui.input.InputModeManager
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
@@ -48,18 +54,24 @@ import androidx.compose.ui.input.key.type
|
|||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.LocalInputModeManager
|
import androidx.compose.ui.platform.LocalInputModeManager
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.ResourceLoader
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Popup
|
import androidx.compose.ui.window.Popup
|
||||||
|
import androidx.compose.ui.window.PopupProperties
|
||||||
import org.jetbrains.jewel.CommonStateBitMask.Active
|
import org.jetbrains.jewel.CommonStateBitMask.Active
|
||||||
import org.jetbrains.jewel.foundation.Stroke
|
import org.jetbrains.jewel.foundation.Stroke
|
||||||
import org.jetbrains.jewel.foundation.border
|
import org.jetbrains.jewel.foundation.border
|
||||||
import org.jetbrains.jewel.foundation.onHover
|
import org.jetbrains.jewel.foundation.onHover
|
||||||
|
import org.jetbrains.jewel.styling.MenuItemColors
|
||||||
|
import org.jetbrains.jewel.styling.MenuItemMetrics
|
||||||
import org.jetbrains.jewel.styling.MenuStyle
|
import org.jetbrains.jewel.styling.MenuStyle
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun MenuContent(
|
internal fun MenuContent(
|
||||||
|
resourceLoader: ResourceLoader,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
style: MenuStyle = IntelliJTheme.menuStyle,
|
style: MenuStyle = IntelliJTheme.menuStyle,
|
||||||
content: MenuScope.() -> Unit,
|
content: MenuScope.() -> Unit,
|
||||||
@@ -80,28 +92,28 @@ internal fun MenuContent(
|
|||||||
elevation = style.metrics.shadowSize,
|
elevation = style.metrics.shadowSize,
|
||||||
shape = menuShape,
|
shape = menuShape,
|
||||||
ambientColor = colors.shadow,
|
ambientColor = colors.shadow,
|
||||||
spotColor = colors.shadow
|
spotColor = colors.shadow,
|
||||||
)
|
)
|
||||||
.border(Stroke.Alignment.Center, style.metrics.borderWidth, colors.border, menuShape)
|
.border(Stroke.Alignment.Center, style.metrics.borderWidth, colors.border, menuShape)
|
||||||
.background(colors.background, menuShape)
|
.background(colors.background, menuShape)
|
||||||
.width(IntrinsicSize.Max)
|
.width(IntrinsicSize.Max)
|
||||||
.onHover {
|
.onHover {
|
||||||
localMenuManager.onHoveredChange(it)
|
localMenuManager.onHoveredChange(it)
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.verticalScroll(scrollState)
|
.verticalScroll(scrollState)
|
||||||
.padding(style.metrics.contentPadding)
|
.padding(style.metrics.contentPadding),
|
||||||
) {
|
) {
|
||||||
items.forEach {
|
items.forEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
is MenuSelectableItem -> {
|
is MenuSelectableItem -> {
|
||||||
MenuSelectableItem(
|
MenuItem(
|
||||||
selected = it.isSelected,
|
selected = it.isSelected,
|
||||||
onClick = it.onClick,
|
onClick = it.onClick,
|
||||||
enabled = it.isEnabled,
|
enabled = it.isEnabled,
|
||||||
content = it.content
|
content = it.content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +121,8 @@ internal fun MenuContent(
|
|||||||
MenuSubmenuItem(
|
MenuSubmenuItem(
|
||||||
enabled = it.isEnabled,
|
enabled = it.isEnabled,
|
||||||
submenu = it.submenu,
|
submenu = it.submenu,
|
||||||
content = it.content
|
content = it.content,
|
||||||
|
resourceLoader = resourceLoader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +135,7 @@ internal fun MenuContent(
|
|||||||
Box(modifier = Modifier.matchParentSize()) {
|
Box(modifier = Modifier.matchParentSize()) {
|
||||||
VerticalScrollbar(
|
VerticalScrollbar(
|
||||||
rememberScrollbarAdapter(scrollState),
|
rememberScrollbarAdapter(scrollState),
|
||||||
modifier = Modifier.fillMaxHeight().align(Alignment.CenterEnd)
|
modifier = Modifier.fillMaxHeight().align(Alignment.CenterEnd),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +159,7 @@ interface MenuScope {
|
|||||||
fun passiveItem(content: @Composable () -> Unit)
|
fun passiveItem(content: @Composable () -> Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MenuScope.divider() {
|
fun MenuScope.separator() {
|
||||||
passiveItem {
|
passiveItem {
|
||||||
MenuSeparator()
|
MenuSeparator()
|
||||||
}
|
}
|
||||||
@@ -193,7 +206,7 @@ private fun (MenuScope.() -> Unit).asList() = buildList {
|
|||||||
override fun submenu(enabled: Boolean, submenu: MenuScope.() -> Unit, content: @Composable () -> Unit) {
|
override fun submenu(enabled: Boolean, submenu: MenuScope.() -> Unit, content: @Composable () -> Unit) {
|
||||||
add(SubmenuItem(enabled, submenu, content))
|
add(SubmenuItem(enabled, submenu, content))
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,17 +235,19 @@ private data class SubmenuItem(
|
|||||||
@Composable
|
@Composable
|
||||||
fun MenuSeparator(
|
fun MenuSeparator(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
style: MenuStyle = IntelliJTheme.menuStyle,
|
metrics: MenuItemMetrics = IntelliJTheme.menuStyle.metrics.itemMetrics,
|
||||||
|
colors: MenuItemColors = IntelliJTheme.menuStyle.colors.itemColors,
|
||||||
) {
|
) {
|
||||||
Divider(
|
Divider(
|
||||||
modifier = modifier.padding(style.metrics.itemMetrics.separatorPadding),
|
modifier = modifier.padding(metrics.separatorPadding),
|
||||||
|
thickness = metrics.separatorThickness,
|
||||||
orientation = Orientation.Horizontal,
|
orientation = Orientation.Horizontal,
|
||||||
color = style.colors.itemColors.separator
|
color = colors.separator,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MenuSelectableItem(
|
fun MenuItem(
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@@ -283,9 +298,9 @@ fun MenuSelectableItem(
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
indication = null
|
indication = null,
|
||||||
)
|
)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
@@ -295,20 +310,19 @@ fun MenuSelectableItem(
|
|||||||
onDispose { }
|
onDispose { }
|
||||||
}
|
}
|
||||||
|
|
||||||
val colors = style.colors.itemColors
|
val itemColors = style.colors.itemColors
|
||||||
val metrics = style.metrics
|
val itemMetrics = style.metrics.itemMetrics
|
||||||
val shape = RoundedCornerShape(metrics.itemMetrics.cornerSize)
|
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalContentColor provides colors.contentFor(itemState).value
|
LocalContentColor provides itemColors.contentFor(itemState).value,
|
||||||
) {
|
) {
|
||||||
Row(
|
val backgroundColor by itemColors.backgroundFor(itemState)
|
||||||
|
|
||||||
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.padding(metrics.itemMetrics.padding)
|
|
||||||
.background(colors.backgroundFor(itemState).value, shape)
|
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(metrics.itemMetrics.contentPadding),
|
.drawItemBackground(itemMetrics, backgroundColor)
|
||||||
verticalAlignment = Alignment.CenterVertically
|
.padding(itemMetrics.contentPadding),
|
||||||
) {
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
@@ -318,6 +332,7 @@ fun MenuSelectableItem(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MenuSubmenuItem(
|
fun MenuSubmenuItem(
|
||||||
|
resourceLoader: ResourceLoader,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
@@ -352,8 +367,14 @@ fun MenuSubmenuItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val itemColors = style.colors.itemColors
|
||||||
|
val menuMetrics = style.metrics
|
||||||
|
|
||||||
|
val backgroundColor by itemColors.backgroundFor(itemState)
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.drawItemBackground(menuMetrics.itemMetrics, backgroundColor)
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
.clickable(
|
.clickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -362,7 +383,7 @@ fun MenuSubmenuItem(
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
indication = null
|
indication = null,
|
||||||
)
|
)
|
||||||
.onKeyEvent {
|
.onKeyEvent {
|
||||||
if (it.type == KeyEventType.KeyDown && it.key == Key.DirectionRight) {
|
if (it.type == KeyEventType.KeyDown && it.key == Key.DirectionRight) {
|
||||||
@@ -371,34 +392,27 @@ fun MenuSubmenuItem(
|
|||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
) {
|
||||||
val colors = style.colors.itemColors
|
|
||||||
val metrics = style.metrics
|
|
||||||
val shape = RoundedCornerShape(metrics.itemMetrics.cornerSize)
|
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalContentColor provides colors.contentFor(itemState).value
|
LocalContentColor provides itemColors.contentFor(itemState).value,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth()
|
||||||
.padding(metrics.itemMetrics.padding)
|
.padding(menuMetrics.itemMetrics.contentPadding),
|
||||||
.background(colors.backgroundFor(itemState).value, shape)
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(metrics.submenuMetrics.itemPadding),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
Box(Modifier.weight(1f)) {
|
Box(Modifier.weight(1f)) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(Modifier.width(24.dp), contentAlignment = Alignment.Center) {
|
val chevronPainter by style.icons.submenuChevron.getPainter(itemState, resourceLoader)
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(style.icons.submenuChevron),
|
painter = chevronPainter,
|
||||||
tint = colors.iconTintFor(itemState).value
|
tint = itemColors.iconTintFor(itemState).value,
|
||||||
)
|
contentDescription = null,
|
||||||
}
|
modifier = Modifier.size(16.dp),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,14 +427,42 @@ fun MenuSubmenuItem(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
style = style,
|
style = style,
|
||||||
content = submenu
|
content = submenu,
|
||||||
|
resourceLoader = resourceLoader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Modifier.drawItemBackground(itemMetrics: MenuItemMetrics, backgroundColor: Color) =
|
||||||
|
drawBehind {
|
||||||
|
val cornerSizePx = itemMetrics.selectionCornerSize.toPx(size, density = this)
|
||||||
|
val cornerRadius = CornerRadius(cornerSizePx, cornerSizePx)
|
||||||
|
|
||||||
|
val outerPadding = itemMetrics.outerPadding
|
||||||
|
val offset = Offset(
|
||||||
|
x = outerPadding.calculateLeftPadding(layoutDirection).toPx(),
|
||||||
|
y = outerPadding.calculateTopPadding().toPx(),
|
||||||
|
)
|
||||||
|
drawRoundRect(
|
||||||
|
color = backgroundColor,
|
||||||
|
cornerRadius = cornerRadius,
|
||||||
|
topLeft = offset,
|
||||||
|
size = size.subtract(outerPadding, density = this, layoutDirection),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Size.subtract(paddingValues: PaddingValues, density: Density, layoutDirection: LayoutDirection): Size =
|
||||||
|
with(density) {
|
||||||
|
Size(
|
||||||
|
width - paddingValues.calculateLeftPadding(layoutDirection).toPx() - paddingValues.calculateRightPadding(layoutDirection).toPx(),
|
||||||
|
height - paddingValues.calculateTopPadding().toPx() - paddingValues.calculateBottomPadding().toPx(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun Submenu(
|
internal fun Submenu(
|
||||||
|
resourceLoader: ResourceLoader,
|
||||||
onDismissRequest: (InputMode) -> Boolean,
|
onDismissRequest: (InputMode) -> Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
style: MenuStyle = IntelliJTheme.menuStyle,
|
style: MenuStyle = IntelliJTheme.menuStyle,
|
||||||
@@ -430,9 +472,9 @@ internal fun Submenu(
|
|||||||
|
|
||||||
val popupPositionProvider = AnchorHorizontalMenuPositionProvider(
|
val popupPositionProvider = AnchorHorizontalMenuPositionProvider(
|
||||||
contentOffset = style.metrics.submenuMetrics.offset,
|
contentOffset = style.metrics.submenuMetrics.offset,
|
||||||
contentMargin = style.metrics.margin,
|
contentMargin = style.metrics.menuMargin,
|
||||||
alignment = Alignment.Top,
|
alignment = Alignment.Top,
|
||||||
density = density
|
density = density,
|
||||||
)
|
)
|
||||||
|
|
||||||
var focusManager: FocusManager? by mutableStateOf(null)
|
var focusManager: FocusManager? by mutableStateOf(null)
|
||||||
@@ -443,16 +485,17 @@ internal fun Submenu(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Popup(
|
Popup(
|
||||||
focusable = true,
|
popupPositionProvider = popupPositionProvider,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
menuManager.closeAll(InputMode.Touch, false)
|
menuManager.closeAll(InputMode.Touch, false)
|
||||||
},
|
},
|
||||||
popupPositionProvider = popupPositionProvider,
|
properties = PopupProperties(focusable = true),
|
||||||
|
onPreviewKeyEvent = { false },
|
||||||
onKeyEvent = {
|
onKeyEvent = {
|
||||||
val currentFocusManager = checkNotNull(focusManager) { "FocusManager must not be null" }
|
val currentFocusManager = checkNotNull(focusManager) { "FocusManager must not be null" }
|
||||||
val currentInputModeManager = checkNotNull(inputModeManager) { "InputModeManager must not be null" }
|
val currentInputModeManager = checkNotNull(inputModeManager) { "InputModeManager must not be null" }
|
||||||
handlePopupMenuOnKeyEvent(it, currentFocusManager, currentInputModeManager, menuManager)
|
handlePopupMenuOnKeyEvent(it, currentFocusManager, currentInputModeManager, menuManager)
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
focusManager = LocalFocusManager.current
|
focusManager = LocalFocusManager.current
|
||||||
inputModeManager = LocalInputModeManager.current
|
inputModeManager = LocalInputModeManager.current
|
||||||
@@ -460,7 +503,8 @@ internal fun Submenu(
|
|||||||
CompositionLocalProvider(LocalMenuManager provides menuManager) {
|
CompositionLocalProvider(LocalMenuManager provides menuManager) {
|
||||||
MenuContent(
|
MenuContent(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
content = content
|
content = content,
|
||||||
|
resourceLoader = resourceLoader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -535,50 +579,3 @@ value class MenuItemState(val state: ULong) : SelectableComponentState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MenuManager(
|
|
||||||
val onDismissRequest: (InputMode) -> Boolean,
|
|
||||||
private val parentMenuManager: MenuManager? = null,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private var isHovered: Boolean = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the hovered state of the menu changes.
|
|
||||||
* This is used to abort parent menu closing in unforced mode
|
|
||||||
* when submenu closed by click parent menu's item.
|
|
||||||
*
|
|
||||||
* @param hovered true if the menu is hovered, false otherwise.
|
|
||||||
*/
|
|
||||||
internal fun onHoveredChange(hovered: Boolean) {
|
|
||||||
isHovered = hovered
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close all menus in the hierarchy.
|
|
||||||
*
|
|
||||||
* @param mode the input mode, menus close by pointer or keyboard event.
|
|
||||||
* @param force true to force close all menus ignore parent hover state, false otherwise.
|
|
||||||
*/
|
|
||||||
fun closeAll(mode: InputMode, force: Boolean) {
|
|
||||||
// We ignore the pointer event if the menu is hovered in unforced mode.
|
|
||||||
if (!force && mode == InputMode.Touch && isHovered) return
|
|
||||||
|
|
||||||
if (onDismissRequest(mode)) {
|
|
||||||
parentMenuManager?.closeAll(mode, force)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun close(mode: InputMode) = onDismissRequest(mode)
|
|
||||||
|
|
||||||
fun isRootMenu(): Boolean = parentMenuManager == null
|
|
||||||
|
|
||||||
fun isSubmenu(): Boolean = parentMenuManager != null
|
|
||||||
|
|
||||||
fun submenuManager(onDismissRequest: (InputMode) -> Boolean) =
|
|
||||||
MenuManager(onDismissRequest = onDismissRequest, parentMenuManager = this)
|
|
||||||
}
|
|
||||||
|
|
||||||
val LocalMenuManager = staticCompositionLocalOf<MenuManager> {
|
|
||||||
error("No MenuManager provided")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.jetbrains.jewel
|
||||||
|
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.compose.ui.input.InputMode
|
||||||
|
|
||||||
|
class MenuManager(
|
||||||
|
val onDismissRequest: (InputMode) -> Boolean,
|
||||||
|
private val parentMenuManager: MenuManager? = null,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private var isHovered: Boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the hovered state of the menu changes.
|
||||||
|
* This is used to abort parent menu closing in unforced mode
|
||||||
|
* when submenu closed by click parent menu's item.
|
||||||
|
*
|
||||||
|
* @param hovered true if the menu is hovered, false otherwise.
|
||||||
|
*/
|
||||||
|
internal fun onHoveredChange(hovered: Boolean) {
|
||||||
|
isHovered = hovered
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all menus in the hierarchy.
|
||||||
|
*
|
||||||
|
* @param mode the input mode, menus close by pointer or keyboard event.
|
||||||
|
* @param force true to force close all menus ignore parent hover state, false otherwise.
|
||||||
|
*/
|
||||||
|
fun closeAll(mode: InputMode, force: Boolean) {
|
||||||
|
// We ignore the pointer event if the menu is hovered in unforced mode.
|
||||||
|
if (!force && mode == InputMode.Touch && isHovered) return
|
||||||
|
|
||||||
|
if (onDismissRequest(mode)) {
|
||||||
|
parentMenuManager?.closeAll(mode, force)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun close(mode: InputMode) = onDismissRequest(mode)
|
||||||
|
|
||||||
|
fun isRootMenu(): Boolean = parentMenuManager == null
|
||||||
|
|
||||||
|
fun isSubmenu(): Boolean = parentMenuManager != null
|
||||||
|
|
||||||
|
fun submenuManager(onDismissRequest: (InputMode) -> Boolean) =
|
||||||
|
MenuManager(onDismissRequest = onDismissRequest, parentMenuManager = this)
|
||||||
|
}
|
||||||
|
|
||||||
|
val LocalMenuManager = staticCompositionLocalOf<MenuManager> {
|
||||||
|
error("No MenuManager provided")
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.jetbrains.jewel
|
package org.jetbrains.jewel
|
||||||
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
@@ -29,10 +28,10 @@ enum class Outline {
|
|||||||
@Composable
|
@Composable
|
||||||
fun Modifier.focusOutline(
|
fun Modifier.focusOutline(
|
||||||
state: FocusableComponentState,
|
state: FocusableComponentState,
|
||||||
outlineShape: Shape = RoundedCornerShape(LocalGlobalMetrics.current.outlineCornerSize),
|
outlineShape: Shape,
|
||||||
outlineWidth: Dp = LocalGlobalMetrics.current.outlineWidth,
|
outlineWidth: Dp = IntelliJTheme.globalMetrics.outlineWidth,
|
||||||
): Modifier {
|
): Modifier {
|
||||||
val outlineColors = LocalGlobalColors.current.outlines
|
val outlineColors = IntelliJTheme.globalColors.outlines
|
||||||
|
|
||||||
return thenIf(state.isFocused) {
|
return thenIf(state.isFocused) {
|
||||||
val outlineColor = outlineColors.focused
|
val outlineColor = outlineColors.focused
|
||||||
@@ -40,34 +39,15 @@ fun Modifier.focusOutline(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Modifier.outline(
|
|
||||||
state: StateWithOutline,
|
|
||||||
outlineShape: Shape = RoundedCornerShape(LocalGlobalMetrics.current.outlineCornerSize),
|
|
||||||
outlineWidth: Dp = LocalGlobalMetrics.current.outlineWidth,
|
|
||||||
): Modifier {
|
|
||||||
val outlineColors = LocalGlobalColors.current.outlines
|
|
||||||
|
|
||||||
return thenIf(state.hasOutline) {
|
|
||||||
val outlineColor = when {
|
|
||||||
state.isError -> outlineColors.focusedError
|
|
||||||
state.isWarning -> outlineColors.focusedWarning
|
|
||||||
state.isFocused -> outlineColors.focused
|
|
||||||
else -> error("State $state says it has an outline, but doesn't really")
|
|
||||||
}
|
|
||||||
border(Stroke.Alignment.Inside, outlineWidth, outlineColor, outlineShape)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Modifier.outline(
|
fun Modifier.outline(
|
||||||
state: FocusableComponentState,
|
state: FocusableComponentState,
|
||||||
outline: Outline,
|
outline: Outline,
|
||||||
|
outlineShape: Shape,
|
||||||
alignment: Stroke.Alignment = Stroke.Alignment.Outside,
|
alignment: Stroke.Alignment = Stroke.Alignment.Outside,
|
||||||
outlineShape: Shape = RoundedCornerShape(LocalGlobalMetrics.current.outlineCornerSize),
|
outlineWidth: Dp = IntelliJTheme.globalMetrics.outlineWidth,
|
||||||
outlineWidth: Dp = LocalGlobalMetrics.current.outlineWidth,
|
|
||||||
): Modifier {
|
): Modifier {
|
||||||
val outlineColors = LocalGlobalColors.current.outlines
|
val outlineColors = IntelliJTheme.globalColors.outlines
|
||||||
|
|
||||||
return thenIf(outline != Outline.None) {
|
return thenIf(outline != Outline.None) {
|
||||||
val outlineColor = when {
|
val outlineColor = when {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ internal data class AnchorVerticalMenuPositionProvider(
|
|||||||
left = leftMargin,
|
left = leftMargin,
|
||||||
top = topMargin,
|
top = topMargin,
|
||||||
right = windowSize.width - rightMargin,
|
right = windowSize.width - rightMargin,
|
||||||
bottom = windowSize.height - bottomMargin
|
bottom = windowSize.height - bottomMargin,
|
||||||
)
|
)
|
||||||
|
|
||||||
// The content offset specified using the dropdown offset parameter.
|
// The content offset specified using the dropdown offset parameter.
|
||||||
@@ -136,7 +136,7 @@ internal data class AnchorHorizontalMenuPositionProvider(
|
|||||||
left = leftMargin,
|
left = leftMargin,
|
||||||
top = topMargin,
|
top = topMargin,
|
||||||
right = windowSize.width - rightMargin,
|
right = windowSize.width - rightMargin,
|
||||||
bottom = windowSize.height - bottomMargin
|
bottom = windowSize.height - bottomMargin,
|
||||||
)
|
)
|
||||||
|
|
||||||
// The content offset specified using the dropdown offset parameter.
|
// The content offset specified using the dropdown offset parameter.
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ fun RadioButton(
|
|||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
style = style,
|
style = style,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
content = null
|
content = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ fun RadioButtonRow(
|
|||||||
resourceLoader = resourceLoader,
|
resourceLoader = resourceLoader,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
style = style,
|
style = style,
|
||||||
textStyle = textStyle
|
textStyle = textStyle,
|
||||||
) {
|
) {
|
||||||
Text(text)
|
Text(text)
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ fun RadioButtonRow(
|
|||||||
style = style,
|
style = style,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
resourceLoader = resourceLoader,
|
resourceLoader = resourceLoader,
|
||||||
content = content
|
content = content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ private fun RadioButtonImpl(
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
role = Role.RadioButton,
|
role = Role.RadioButton,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
indication = null
|
indication = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
val colors = style.colors
|
val colors = style.colors
|
||||||
@@ -174,14 +174,14 @@ private fun RadioButtonImpl(
|
|||||||
Row(
|
Row(
|
||||||
wrapperModifier,
|
wrapperModifier,
|
||||||
horizontalArrangement = Arrangement.spacedBy(metrics.iconContentGap),
|
horizontalArrangement = Arrangement.spacedBy(metrics.iconContentGap),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
RadioButtonImage(Modifier, radioButtonPainter, radioButtonModifier)
|
RadioButtonImage(Modifier, radioButtonPainter, radioButtonModifier)
|
||||||
|
|
||||||
val contentColor by colors.contentFor(radioButtonState)
|
val contentColor by colors.contentFor(radioButtonState)
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalTextStyle provides textStyle.copy(color = contentColor.takeOrElse { textStyle.color }),
|
LocalTextStyle provides textStyle.copy(color = contentColor.takeOrElse { textStyle.color }),
|
||||||
LocalContentColor provides contentColor.takeOrElse { textStyle.color }
|
LocalContentColor provides contentColor.takeOrElse { textStyle.color },
|
||||||
) {
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
@@ -238,7 +238,7 @@ value class RadioButtonState(val state: ULong) : SelectableComponentState {
|
|||||||
focused = focused,
|
focused = focused,
|
||||||
pressed = pressed,
|
pressed = pressed,
|
||||||
hovered = hovered,
|
hovered = hovered,
|
||||||
active = active
|
active = active,
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun toString() =
|
override fun toString() =
|
||||||
@@ -264,7 +264,7 @@ value class RadioButtonState(val state: ULong) : SelectableComponentState {
|
|||||||
(if (focused) Focused else 0UL) or
|
(if (focused) Focused else 0UL) or
|
||||||
(if (pressed) Pressed else 0UL) or
|
(if (pressed) Pressed else 0UL) or
|
||||||
(if (hovered) Hovered else 0UL) or
|
(if (hovered) Hovered else 0UL) or
|
||||||
(if (active) Active else 0UL)
|
(if (active) Active else 0UL),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.jetbrains.jewel
|
||||||
|
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.compose.ui.res.ResourceLoader
|
||||||
|
|
||||||
|
val LocalResourceLoader = staticCompositionLocalOf<ResourceLoader> {
|
||||||
|
ResourceLoader.Default
|
||||||
|
}
|
||||||
@@ -36,15 +36,15 @@ fun VerticalScrollbar(
|
|||||||
shape = shape,
|
shape = shape,
|
||||||
hoverDurationMillis = hoverDurationMillis,
|
hoverDurationMillis = hoverDurationMillis,
|
||||||
unhoverColor = style.colors.thumbBackground,
|
unhoverColor = style.colors.thumbBackground,
|
||||||
hoverColor = style.colors.thumbBackgroundHovered
|
hoverColor = style.colors.thumbBackgroundHovered,
|
||||||
)
|
),
|
||||||
) {
|
) {
|
||||||
VerticalScrollbar(
|
VerticalScrollbar(
|
||||||
adapter = adapter,
|
adapter = adapter,
|
||||||
modifier = modifier.padding(style.metrics.trackPadding),
|
modifier = modifier.padding(style.metrics.trackPadding),
|
||||||
reverseLayout = reverseLayout,
|
reverseLayout = reverseLayout,
|
||||||
style = LocalScrollbarStyle.current,
|
style = LocalScrollbarStyle.current,
|
||||||
interactionSource = interactionSource
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,15 +67,15 @@ fun HorizontalScrollbar(
|
|||||||
shape = shape,
|
shape = shape,
|
||||||
hoverDurationMillis = hoverDurationMillis,
|
hoverDurationMillis = hoverDurationMillis,
|
||||||
unhoverColor = style.colors.thumbBackground,
|
unhoverColor = style.colors.thumbBackground,
|
||||||
hoverColor = style.colors.thumbBackgroundHovered
|
hoverColor = style.colors.thumbBackgroundHovered,
|
||||||
)
|
),
|
||||||
) {
|
) {
|
||||||
HorizontalScrollbar(
|
HorizontalScrollbar(
|
||||||
adapter = adapter,
|
adapter = adapter,
|
||||||
modifier = modifier.padding(style.metrics.trackPadding),
|
modifier = modifier.padding(style.metrics.trackPadding),
|
||||||
reverseLayout = reverseLayout,
|
reverseLayout = reverseLayout,
|
||||||
style = LocalScrollbarStyle.current,
|
style = LocalScrollbarStyle.current,
|
||||||
interactionSource = interactionSource
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,15 +98,15 @@ fun TabStripHorizontalScrollbar(
|
|||||||
shape = shape,
|
shape = shape,
|
||||||
hoverDurationMillis = hoverDurationMillis,
|
hoverDurationMillis = hoverDurationMillis,
|
||||||
unhoverColor = style.colors.thumbBackground,
|
unhoverColor = style.colors.thumbBackground,
|
||||||
hoverColor = style.colors.thumbBackgroundHovered
|
hoverColor = style.colors.thumbBackgroundHovered,
|
||||||
)
|
),
|
||||||
) {
|
) {
|
||||||
HorizontalScrollbar(
|
HorizontalScrollbar(
|
||||||
adapter = adapter,
|
adapter = adapter,
|
||||||
modifier = modifier.padding(1.dp),
|
modifier = modifier.padding(1.dp),
|
||||||
reverseLayout = reverseLayout,
|
reverseLayout = reverseLayout,
|
||||||
style = LocalScrollbarStyle.current,
|
style = LocalScrollbarStyle.current,
|
||||||
interactionSource = interactionSource
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
package org.jetbrains.jewel
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
|
|
||||||
interface StateWithOutline : FocusableComponentState {
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
val isError: Boolean
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
val isWarning: Boolean
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
val hasOutline: Boolean
|
|
||||||
get() = outline != Outline.None
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
val outline: Outline
|
|
||||||
get() = when {
|
|
||||||
isError -> Outline.Error
|
|
||||||
isWarning -> Outline.Warning
|
|
||||||
else -> Outline.None
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun <T> chooseValueWithOutline(
|
|
||||||
normal: T,
|
|
||||||
disabled: T,
|
|
||||||
focused: T,
|
|
||||||
pressed: T,
|
|
||||||
hovered: T,
|
|
||||||
warning: T,
|
|
||||||
error: T,
|
|
||||||
active: T,
|
|
||||||
): T =
|
|
||||||
when {
|
|
||||||
!isEnabled -> disabled
|
|
||||||
isPressed && !IntelliJTheme.isSwingCompatMode -> pressed
|
|
||||||
isHovered && !IntelliJTheme.isSwingCompatMode -> hovered
|
|
||||||
isFocused -> focused
|
|
||||||
isError -> error
|
|
||||||
isWarning -> warning
|
|
||||||
isActive -> active
|
|
||||||
else -> normal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -42,7 +42,7 @@ fun TabStrip(
|
|||||||
Box(
|
Box(
|
||||||
modifier
|
modifier
|
||||||
.focusable(true, remember { MutableInteractionSource() })
|
.focusable(true, remember { MutableInteractionSource() })
|
||||||
.onHover { tabStripState = tabStripState.copy(hovered = it) }
|
.onHover { tabStripState = tabStripState.copy(hovered = it) },
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -52,12 +52,12 @@ fun TabStrip(
|
|||||||
reverseDirection = ScrollableDefaults.reverseDirection(
|
reverseDirection = ScrollableDefaults.reverseDirection(
|
||||||
LocalLayoutDirection.current,
|
LocalLayoutDirection.current,
|
||||||
Orientation.Vertical,
|
Orientation.Vertical,
|
||||||
false
|
false,
|
||||||
),
|
),
|
||||||
state = scrollState,
|
state = scrollState,
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
)
|
)
|
||||||
.selectableGroup()
|
.selectableGroup(),
|
||||||
) {
|
) {
|
||||||
tabs.forEach {
|
tabs.forEach {
|
||||||
TabImpl(isActive = tabStripState.isActive, tabData = it)
|
TabImpl(isActive = tabStripState.isActive, tabData = it)
|
||||||
@@ -69,20 +69,20 @@ fun TabStrip(
|
|||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
durationMillis = 125,
|
durationMillis = 125,
|
||||||
delayMillis = 0,
|
delayMillis = 0,
|
||||||
easing = LinearEasing
|
easing = LinearEasing,
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
exit = fadeOut(
|
exit = fadeOut(
|
||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
durationMillis = 125,
|
durationMillis = 125,
|
||||||
delayMillis = 700,
|
delayMillis = 700,
|
||||||
easing = LinearEasing
|
easing = LinearEasing,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
) {
|
) {
|
||||||
TabStripHorizontalScrollbar(
|
TabStripHorizontalScrollbar(
|
||||||
adapter = rememberScrollbarAdapter(scrollState),
|
adapter = rememberScrollbarAdapter(scrollState),
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ value class TabStripState(val state: ULong) : FocusableComponentState {
|
|||||||
focused = focused,
|
focused = focused,
|
||||||
pressed = pressed,
|
pressed = pressed,
|
||||||
hovered = hovered,
|
hovered = hovered,
|
||||||
active = active
|
active = active,
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun toString() =
|
override fun toString() =
|
||||||
@@ -232,7 +232,7 @@ value class TabStripState(val state: ULong) : FocusableComponentState {
|
|||||||
(if (pressed) CommonStateBitMask.Pressed else 0UL) or
|
(if (pressed) CommonStateBitMask.Pressed else 0UL) or
|
||||||
(if (warning) CommonStateBitMask.Warning else 0UL) or
|
(if (warning) CommonStateBitMask.Warning else 0UL) or
|
||||||
(if (error) CommonStateBitMask.Error else 0UL) or
|
(if (error) CommonStateBitMask.Error else 0UL) or
|
||||||
(if (active) CommonStateBitMask.Active else 0UL)
|
(if (active) CommonStateBitMask.Active else 0UL),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ internal fun TabImpl(
|
|||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalIndication provides NoIndication,
|
LocalIndication provides NoIndication,
|
||||||
LocalContentColor provides tabStyle.colors.contentFor(tabState).value
|
LocalContentColor provides tabStyle.colors.contentFor(tabState).value,
|
||||||
) {
|
) {
|
||||||
val labelAlpha by tabStyle.contentAlpha.labelFor(tabState)
|
val labelAlpha by tabStyle.contentAlpha.labelFor(tabState)
|
||||||
val iconAlpha by tabStyle.contentAlpha.iconFor(tabState)
|
val iconAlpha by tabStyle.contentAlpha.iconFor(tabState)
|
||||||
@@ -92,7 +92,7 @@ internal fun TabImpl(
|
|||||||
selected = tabData.selected,
|
selected = tabData.selected,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
indication = NoIndication,
|
indication = NoIndication,
|
||||||
role = Role.Tab
|
role = Role.Tab,
|
||||||
)
|
)
|
||||||
.drawBehind {
|
.drawBehind {
|
||||||
val strokeThickness = lineThickness.toPx()
|
val strokeThickness = lineThickness.toPx()
|
||||||
@@ -105,12 +105,12 @@ internal fun TabImpl(
|
|||||||
start = Offset(0 + capDxFix, startY),
|
start = Offset(0 + capDxFix, startY),
|
||||||
end = Offset(endX - capDxFix, startY),
|
end = Offset(endX - capDxFix, startY),
|
||||||
strokeWidth = strokeThickness,
|
strokeWidth = strokeThickness,
|
||||||
cap = StrokeCap.Round
|
cap = StrokeCap.Round,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding(tabStyle.metrics.tabPadding),
|
.padding(tabStyle.metrics.tabPadding),
|
||||||
horizontalArrangement = Arrangement.spacedBy(tabStyle.metrics.closeContentGap),
|
horizontalArrangement = Arrangement.spacedBy(tabStyle.metrics.closeContentGap),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
tabData.tabIconResource?.let { icon ->
|
tabData.tabIconResource?.let { icon ->
|
||||||
val iconPainter = painterResource(icon, LocalResourceLoader.current)
|
val iconPainter = painterResource(icon, LocalResourceLoader.current)
|
||||||
@@ -120,7 +120,7 @@ internal fun TabImpl(
|
|||||||
Text(
|
Text(
|
||||||
modifier = Modifier.alpha(labelAlpha),
|
modifier = Modifier.alpha(labelAlpha),
|
||||||
text = tabData.label,
|
text = tabData.label,
|
||||||
color = tabStyle.colors.contentFor(tabState).value
|
color = tabStyle.colors.contentFor(tabState).value,
|
||||||
)
|
)
|
||||||
val showCloseIcon = when (tabData) {
|
val showCloseIcon = when (tabData) {
|
||||||
is TabData.Default -> tabData.closable
|
is TabData.Default -> tabData.closable
|
||||||
@@ -147,10 +147,10 @@ internal fun TabImpl(
|
|||||||
interactionSource = closeActionInteractionSource,
|
interactionSource = closeActionInteractionSource,
|
||||||
indication = null,
|
indication = null,
|
||||||
onClick = tabData.onClose,
|
onClick = tabData.onClose,
|
||||||
role = Role.Button
|
role = Role.Button,
|
||||||
).size(16.dp),
|
).size(16.dp),
|
||||||
painter = closePainter,
|
painter = closePainter,
|
||||||
contentDescription = "Close tab ${tabData.label}"
|
contentDescription = "Close tab ${tabData.label}",
|
||||||
)
|
)
|
||||||
} else if (tabData.closable) {
|
} else if (tabData.closable) {
|
||||||
Spacer(Modifier.size(16.dp))
|
Spacer(Modifier.size(16.dp))
|
||||||
@@ -215,7 +215,7 @@ value class TabState(val state: ULong) : SelectableComponentState {
|
|||||||
focused = focused,
|
focused = focused,
|
||||||
pressed = pressed,
|
pressed = pressed,
|
||||||
hovered = hovered,
|
hovered = hovered,
|
||||||
active = active
|
active = active,
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun toString() =
|
override fun toString() =
|
||||||
@@ -237,7 +237,7 @@ value class TabState(val state: ULong) : SelectableComponentState {
|
|||||||
(if (focused) Focused else 0UL) or
|
(if (focused) Focused else 0UL) or
|
||||||
(if (pressed) Pressed else 0UL) or
|
(if (pressed) Pressed else 0UL) or
|
||||||
(if (hovered) Hovered else 0UL) or
|
(if (hovered) Hovered else 0UL) or
|
||||||
(if (active) Active else 0UL)
|
(if (active) Active else 0UL),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.jetbrains.jewel
|
|||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
import androidx.compose.foundation.text.InlineTextContent
|
import androidx.compose.foundation.text.InlineTextContent
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -36,7 +35,7 @@ fun Text(
|
|||||||
softWrap: Boolean = true,
|
softWrap: Boolean = true,
|
||||||
maxLines: Int = Int.MAX_VALUE,
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||||
style: TextStyle = LocalTextStyle.current,
|
style: TextStyle = IntelliJTheme.defaultTextStyle,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
AnnotatedString(text),
|
AnnotatedString(text),
|
||||||
@@ -55,7 +54,7 @@ fun Text(
|
|||||||
maxLines,
|
maxLines,
|
||||||
emptyMap(),
|
emptyMap(),
|
||||||
onTextLayout,
|
onTextLayout,
|
||||||
style
|
style,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +76,7 @@ fun Text(
|
|||||||
maxLines: Int = Int.MAX_VALUE,
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
inlineContent: Map<String, InlineTextContent> = emptyMap(),
|
inlineContent: Map<String, InlineTextContent> = emptyMap(),
|
||||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||||
style: TextStyle = LocalTextStyle.current,
|
style: TextStyle = IntelliJTheme.defaultTextStyle,
|
||||||
) {
|
) {
|
||||||
val textColor = color.takeOrElse {
|
val textColor = color.takeOrElse {
|
||||||
style.color.takeOrElse {
|
style.color.takeOrElse {
|
||||||
@@ -97,8 +96,8 @@ fun Text(
|
|||||||
fontFamily = fontFamily,
|
fontFamily = fontFamily,
|
||||||
textDecoration = textDecoration,
|
textDecoration = textDecoration,
|
||||||
fontStyle = fontStyle,
|
fontStyle = fontStyle,
|
||||||
letterSpacing = letterSpacing
|
letterSpacing = letterSpacing,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
BasicText(text, modifier, mergedStyle, onTextLayout, overflow, softWrap, maxLines, minLines = 1, inlineContent)
|
BasicText(text, modifier, mergedStyle, onTextLayout, overflow, softWrap, maxLines, minLines = 1, inlineContent)
|
||||||
}
|
}
|
||||||
@@ -110,9 +109,3 @@ val LocalTextStyle = staticCompositionLocalOf<TextStyle> {
|
|||||||
val LocalContentColor = staticCompositionLocalOf<Color> {
|
val LocalContentColor = staticCompositionLocalOf<Color> {
|
||||||
error("No ContentColor provided")
|
error("No ContentColor provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ProvideTextStyle(value: TextStyle, content: @Composable () -> Unit) {
|
|
||||||
val mergedStyle = LocalTextStyle.current.merge(value)
|
|
||||||
CompositionLocalProvider(LocalTextStyle provides mergedStyle, content = content)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -24,12 +23,13 @@ import androidx.compose.ui.text.TextStyle
|
|||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.unit.Constraints
|
import androidx.compose.ui.unit.Constraints
|
||||||
import androidx.compose.ui.unit.Density
|
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.offset
|
import androidx.compose.ui.unit.offset
|
||||||
import org.jetbrains.jewel.styling.TextAreaStyle
|
import org.jetbrains.jewel.styling.TextAreaStyle
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param placeholder the optional placeholder to be displayed over the
|
||||||
|
* component when the [value] is empty.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun TextArea(
|
fun TextArea(
|
||||||
value: String,
|
value: String,
|
||||||
@@ -37,9 +37,8 @@ fun TextArea(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
isError: Boolean = false,
|
outline: Outline = Outline.None,
|
||||||
placeholder: @Composable (() -> Unit)? = null,
|
placeholder: @Composable (() -> Unit)? = null,
|
||||||
hint: @Composable (() -> Unit)? = null,
|
|
||||||
undecorated: Boolean = false,
|
undecorated: Boolean = false,
|
||||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||||
@@ -69,9 +68,8 @@ fun TextArea(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
isError = isError,
|
outline = outline,
|
||||||
placeholder = placeholder,
|
placeholder = placeholder,
|
||||||
hint = hint,
|
|
||||||
undecorated = undecorated,
|
undecorated = undecorated,
|
||||||
visualTransformation = visualTransformation,
|
visualTransformation = visualTransformation,
|
||||||
keyboardOptions = keyboardOptions,
|
keyboardOptions = keyboardOptions,
|
||||||
@@ -80,10 +78,14 @@ fun TextArea(
|
|||||||
onTextLayout = onTextLayout,
|
onTextLayout = onTextLayout,
|
||||||
style = style,
|
style = style,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
interactionSource = interactionSource
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param placeholder the optional placeholder to be displayed over the
|
||||||
|
* component when the [value] is empty.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun TextArea(
|
fun TextArea(
|
||||||
value: TextFieldValue,
|
value: TextFieldValue,
|
||||||
@@ -92,9 +94,8 @@ fun TextArea(
|
|||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
placeholder: @Composable (() -> Unit)? = null,
|
placeholder: @Composable (() -> Unit)? = null,
|
||||||
hint: @Composable (() -> Unit)? = null,
|
|
||||||
undecorated: Boolean = false,
|
undecorated: Boolean = false,
|
||||||
isError: Boolean = false,
|
outline: Outline = Outline.None,
|
||||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||||
keyboardActions: KeyboardActions = KeyboardActions(),
|
keyboardActions: KeyboardActions = KeyboardActions(),
|
||||||
@@ -104,13 +105,15 @@ fun TextArea(
|
|||||||
textStyle: TextStyle = IntelliJTheme.defaultTextStyle,
|
textStyle: TextStyle = IntelliJTheme.defaultTextStyle,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
) {
|
) {
|
||||||
|
val minSize = style.metrics.minSize
|
||||||
InputField(
|
InputField(
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = onValueChange,
|
onValueChange = onValueChange,
|
||||||
modifier = modifier,
|
modifier = modifier
|
||||||
|
.defaultMinSize(minWidth = minSize.width, minHeight = minSize.height),
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
isError = isError,
|
outline = outline,
|
||||||
undecorated = undecorated,
|
undecorated = undecorated,
|
||||||
visualTransformation = visualTransformation,
|
visualTransformation = visualTransformation,
|
||||||
keyboardOptions = keyboardOptions,
|
keyboardOptions = keyboardOptions,
|
||||||
@@ -120,108 +123,89 @@ fun TextArea(
|
|||||||
onTextLayout = onTextLayout,
|
onTextLayout = onTextLayout,
|
||||||
style = style,
|
style = style,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
interactionSource = interactionSource
|
interactionSource = interactionSource,
|
||||||
) { innerTextField, state ->
|
) { innerTextField, state ->
|
||||||
val minSize = style.metrics.minSize
|
|
||||||
|
|
||||||
TextAreaDecorationBox(
|
TextAreaDecorationBox(
|
||||||
modifier = Modifier
|
|
||||||
.defaultMinSize(minWidth = minSize.width, minHeight = minSize.height),
|
|
||||||
innerTextField = innerTextField,
|
innerTextField = innerTextField,
|
||||||
contentPadding = style.metrics.contentPadding,
|
contentPadding = style.metrics.contentPadding,
|
||||||
placeholderTextColor = style.colors.placeholder,
|
placeholderTextColor = style.colors.placeholder,
|
||||||
placeholder = if (value.text.isEmpty()) placeholder else null,
|
placeholder = if (value.text.isEmpty()) placeholder else null,
|
||||||
hintTextStyle = style.hintTextStyle,
|
textStyle = textStyle,
|
||||||
hintTextColor = style.colors.hintContentFor(state).value,
|
|
||||||
hint = hint
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TextAreaDecorationBox(
|
private fun TextAreaDecorationBox(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
innerTextField: @Composable () -> Unit,
|
innerTextField: @Composable () -> Unit,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
|
textStyle: TextStyle,
|
||||||
placeholderTextColor: Color,
|
placeholderTextColor: Color,
|
||||||
placeholder: @Composable (() -> Unit)?,
|
placeholder: @Composable (() -> Unit)?,
|
||||||
hintTextStyle: TextStyle,
|
|
||||||
hintTextColor: Color,
|
|
||||||
hint: @Composable (() -> Unit)?,
|
|
||||||
) {
|
) {
|
||||||
Layout(
|
Layout(
|
||||||
modifier = modifier,
|
|
||||||
content = {
|
content = {
|
||||||
if (placeholder != null) {
|
if (placeholder != null) {
|
||||||
Box(modifier = Modifier.layoutId(PLACEHOLDER_ID), contentAlignment = Alignment.Center) {
|
Box(
|
||||||
|
modifier = Modifier.layoutId(PLACEHOLDER_ID),
|
||||||
|
contentAlignment = Alignment.CenterStart,
|
||||||
|
) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
|
LocalTextStyle provides textStyle.copy(color = placeholderTextColor),
|
||||||
LocalContentColor provides placeholderTextColor,
|
LocalContentColor provides placeholderTextColor,
|
||||||
content = placeholder
|
content = placeholder,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier = Modifier.layoutId(TEXT_FIELD_ID), propagateMinConstraints = true) {
|
Box(
|
||||||
|
modifier = Modifier.layoutId(TEXT_AREA_ID),
|
||||||
|
contentAlignment = Alignment.CenterStart,
|
||||||
|
propagateMinConstraints = true,
|
||||||
|
) {
|
||||||
innerTextField()
|
innerTextField()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
if (hint != null) {
|
|
||||||
Box(Modifier.layoutId(HINT_ID).fillMaxWidth()) {
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalTextStyle provides hintTextStyle,
|
|
||||||
LocalContentColor provides hintTextColor,
|
|
||||||
content = hint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) { measurables, incomingConstraints ->
|
) { measurables, incomingConstraints ->
|
||||||
val horizontalPadding =
|
val horizontalPadding =
|
||||||
(contentPadding.calculateLeftPadding(layoutDirection) + contentPadding.calculateRightPadding(layoutDirection)).roundToPx()
|
(
|
||||||
|
contentPadding.calculateLeftPadding(layoutDirection) +
|
||||||
|
contentPadding.calculateRightPadding(layoutDirection)
|
||||||
|
).roundToPx()
|
||||||
val verticalPadding =
|
val verticalPadding =
|
||||||
(contentPadding.calculateTopPadding() + contentPadding.calculateBottomPadding()).roundToPx()
|
(
|
||||||
|
contentPadding.calculateTopPadding() +
|
||||||
|
contentPadding.calculateBottomPadding()
|
||||||
|
).roundToPx()
|
||||||
|
|
||||||
// measure hint
|
val textAreaConstraints = incomingConstraints.offset(
|
||||||
val hintConstraints = incomingConstraints.copy(minHeight = 0)
|
horizontal = -horizontalPadding,
|
||||||
val hintPlaceable = measurables.find { it.layoutId == HINT_ID }?.measure(hintConstraints)
|
vertical = -verticalPadding,
|
||||||
val occupiedSpaceVertically = hintPlaceable?.height ?: 0
|
).copy(minHeight = 0)
|
||||||
|
|
||||||
val constraintsWithoutPadding = incomingConstraints.offset(
|
val textAreaPlaceable = measurables.first { it.layoutId == TEXT_AREA_ID }.measure(textAreaConstraints)
|
||||||
-horizontalPadding,
|
|
||||||
-verticalPadding - occupiedSpaceVertically
|
|
||||||
)
|
|
||||||
|
|
||||||
val textConstraints = constraintsWithoutPadding
|
// Measure placeholder
|
||||||
val textFieldPlaceable = measurables.first { it.layoutId == TEXT_FIELD_ID }.measure(textConstraints)
|
val placeholderConstraints = textAreaConstraints.copy(minWidth = 0, minHeight = 0)
|
||||||
|
|
||||||
// measure placeholder
|
|
||||||
val placeholderConstraints = textConstraints.copy(minWidth = 0, minHeight = 0)
|
|
||||||
val placeholderPlaceable = measurables.find { it.layoutId == PLACEHOLDER_ID }?.measure(placeholderConstraints)
|
val placeholderPlaceable = measurables.find { it.layoutId == PLACEHOLDER_ID }?.measure(placeholderConstraints)
|
||||||
|
|
||||||
val width = calculateWidth(
|
val width = calculateWidth(
|
||||||
textFieldPlaceable,
|
textAreaPlaceable,
|
||||||
placeholderPlaceable,
|
placeholderPlaceable,
|
||||||
horizontalPadding,
|
textAreaConstraints,
|
||||||
hintPlaceable,
|
|
||||||
constraintsWithoutPadding
|
|
||||||
)
|
)
|
||||||
val height = calculateHeight(
|
val height = calculateHeight(
|
||||||
textFieldPlaceable,
|
textAreaPlaceable,
|
||||||
placeholderPlaceable,
|
placeholderPlaceable,
|
||||||
verticalPadding,
|
verticalPadding,
|
||||||
hintPlaceable,
|
textAreaConstraints,
|
||||||
constraintsWithoutPadding
|
|
||||||
)
|
)
|
||||||
|
|
||||||
layout(width, height) {
|
layout(width, height) {
|
||||||
place(
|
place(
|
||||||
height,
|
height,
|
||||||
contentPadding,
|
textAreaPlaceable,
|
||||||
hintPlaceable,
|
|
||||||
textFieldPlaceable,
|
|
||||||
placeholderPlaceable,
|
placeholderPlaceable,
|
||||||
layoutDirection,
|
|
||||||
this@Layout
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,55 +214,44 @@ private fun TextAreaDecorationBox(
|
|||||||
private fun calculateWidth(
|
private fun calculateWidth(
|
||||||
textFieldPlaceable: Placeable,
|
textFieldPlaceable: Placeable,
|
||||||
placeholderPlaceable: Placeable?,
|
placeholderPlaceable: Placeable?,
|
||||||
horizontalPadding: Int,
|
|
||||||
hintPlaceable: Placeable?,
|
|
||||||
constraints: Constraints,
|
constraints: Constraints,
|
||||||
): Int {
|
): Int =
|
||||||
return maxOf(
|
maxOf(
|
||||||
textFieldPlaceable.width + horizontalPadding,
|
textFieldPlaceable.width,
|
||||||
(placeholderPlaceable?.width ?: 0) + horizontalPadding,
|
placeholderPlaceable?.width ?: 0,
|
||||||
hintPlaceable?.width ?: 0,
|
|
||||||
constraints.minWidth
|
|
||||||
)
|
)
|
||||||
}
|
.coerceAtLeast(constraints.minWidth)
|
||||||
|
|
||||||
private fun calculateHeight(
|
private fun calculateHeight(
|
||||||
textFieldPlaceable: Placeable,
|
textFieldPlaceable: Placeable,
|
||||||
placeholderPlaceable: Placeable?,
|
placeholderPlaceable: Placeable?,
|
||||||
verticalPadding: Int,
|
verticalPadding: Int,
|
||||||
hintPlaceable: Placeable?,
|
|
||||||
constraints: Constraints,
|
constraints: Constraints,
|
||||||
): Int {
|
): Int {
|
||||||
val middleSection = maxOf(
|
val textAreaHeight = maxOf(
|
||||||
textFieldPlaceable.height,
|
textFieldPlaceable.height,
|
||||||
placeholderPlaceable?.height ?: 0
|
placeholderPlaceable?.height ?: 0,
|
||||||
) + verticalPadding
|
)
|
||||||
val wrappedHeight = (hintPlaceable?.height ?: 0) + middleSection
|
return (textAreaHeight + verticalPadding).coerceAtLeast(constraints.minHeight)
|
||||||
return max(wrappedHeight, constraints.minHeight)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Placeable.PlacementScope.place(
|
private fun Placeable.PlacementScope.place(
|
||||||
height: Int,
|
height: Int,
|
||||||
contentPadding: PaddingValues,
|
textAreaPlaceable: Placeable,
|
||||||
hintPlaceable: Placeable?,
|
|
||||||
textFieldPlaceable: Placeable,
|
|
||||||
placeholderPlaceable: Placeable?,
|
placeholderPlaceable: Placeable?,
|
||||||
layoutDirection: LayoutDirection,
|
) {
|
||||||
density: Density,
|
// placed center vertically
|
||||||
) = with(density) {
|
textAreaPlaceable.placeRelative(
|
||||||
hintPlaceable?.placeRelative(
|
|
||||||
0,
|
0,
|
||||||
height - hintPlaceable.height
|
Alignment.CenterVertically.align(textAreaPlaceable.height, height),
|
||||||
)
|
)
|
||||||
|
|
||||||
val y = contentPadding.calculateTopPadding().roundToPx()
|
// placed similar to the input text above
|
||||||
val x = contentPadding.calculateLeftPadding(layoutDirection).roundToPx()
|
placeholderPlaceable?.placeRelative(
|
||||||
|
0,
|
||||||
textFieldPlaceable.placeRelative(x, y)
|
Alignment.CenterVertically.align(placeholderPlaceable.height, height),
|
||||||
|
)
|
||||||
placeholderPlaceable?.placeRelative(x, y)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val PLACEHOLDER_ID = "Placeholder"
|
private const val PLACEHOLDER_ID = "Placeholder"
|
||||||
private const val TEXT_FIELD_ID = "TextField"
|
private const val TEXT_AREA_ID = "TextField"
|
||||||
private const val HINT_ID = "Hint"
|
|
||||||
|
|||||||
@@ -23,11 +23,14 @@ import androidx.compose.ui.text.TextStyle
|
|||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.unit.Constraints
|
import androidx.compose.ui.unit.Constraints
|
||||||
import androidx.compose.ui.unit.Density
|
|
||||||
import androidx.compose.ui.unit.offset
|
import androidx.compose.ui.unit.offset
|
||||||
import org.jetbrains.jewel.styling.TextFieldStyle
|
import org.jetbrains.jewel.styling.TextFieldStyle
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param placeholder the optional placeholder to be displayed over the
|
||||||
|
* component when the [value] is empty.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun TextField(
|
fun TextField(
|
||||||
value: String,
|
value: String,
|
||||||
@@ -35,7 +38,7 @@ fun TextField(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
isError: Boolean = false,
|
outline: Outline = Outline.None,
|
||||||
placeholder: @Composable (() -> Unit)? = null,
|
placeholder: @Composable (() -> Unit)? = null,
|
||||||
trailingIcon: @Composable (() -> Unit)? = null,
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
undecorated: Boolean = false,
|
undecorated: Boolean = false,
|
||||||
@@ -65,7 +68,7 @@ fun TextField(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
isError = isError,
|
outline = outline,
|
||||||
placeholder = placeholder,
|
placeholder = placeholder,
|
||||||
trailingIcon = trailingIcon,
|
trailingIcon = trailingIcon,
|
||||||
undecorated = undecorated,
|
undecorated = undecorated,
|
||||||
@@ -74,10 +77,14 @@ fun TextField(
|
|||||||
keyboardActions = keyboardActions,
|
keyboardActions = keyboardActions,
|
||||||
onTextLayout = onTextLayout,
|
onTextLayout = onTextLayout,
|
||||||
style = style,
|
style = style,
|
||||||
interactionSource = interactionSource
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param placeholder the optional placeholder to be displayed over the
|
||||||
|
* component when the [value] is empty.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun TextField(
|
fun TextField(
|
||||||
value: TextFieldValue,
|
value: TextFieldValue,
|
||||||
@@ -85,7 +92,7 @@ fun TextField(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
isError: Boolean = false,
|
outline: Outline = Outline.None,
|
||||||
placeholder: @Composable (() -> Unit)? = null,
|
placeholder: @Composable (() -> Unit)? = null,
|
||||||
trailingIcon: @Composable (() -> Unit)? = null,
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
undecorated: Boolean = false,
|
undecorated: Boolean = false,
|
||||||
@@ -103,7 +110,7 @@ fun TextField(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
isError = isError,
|
outline = outline,
|
||||||
undecorated = undecorated,
|
undecorated = undecorated,
|
||||||
visualTransformation = visualTransformation,
|
visualTransformation = visualTransformation,
|
||||||
keyboardOptions = keyboardOptions,
|
keyboardOptions = keyboardOptions,
|
||||||
@@ -113,17 +120,18 @@ fun TextField(
|
|||||||
onTextLayout = onTextLayout,
|
onTextLayout = onTextLayout,
|
||||||
style = style,
|
style = style,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
interactionSource = interactionSource
|
interactionSource = interactionSource,
|
||||||
) { innerTextField, _ ->
|
) { innerTextField, _ ->
|
||||||
val minSize = style.metrics.minSize
|
val minSize = style.metrics.minSize
|
||||||
|
|
||||||
TextFieldDecorationBox(
|
TextFieldDecorationBox(
|
||||||
modifier = Modifier.defaultMinSize(minHeight = minSize.width, minWidth = minSize.height)
|
modifier = Modifier.defaultMinSize(minWidth = minSize.width, minHeight = minSize.height)
|
||||||
.padding(style.metrics.contentPadding),
|
.padding(style.metrics.contentPadding),
|
||||||
innerTextField = innerTextField,
|
innerTextField = innerTextField,
|
||||||
|
textStyle = textStyle,
|
||||||
placeholderTextColor = style.colors.placeholder,
|
placeholderTextColor = style.colors.placeholder,
|
||||||
placeholder = if (value.text.isEmpty()) placeholder else null,
|
placeholder = if (value.text.isEmpty()) placeholder else null,
|
||||||
trailingIcon = trailingIcon
|
trailingIcon = trailingIcon,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,6 +140,7 @@ fun TextField(
|
|||||||
private fun TextFieldDecorationBox(
|
private fun TextFieldDecorationBox(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
innerTextField: @Composable () -> Unit,
|
innerTextField: @Composable () -> Unit,
|
||||||
|
textStyle: TextStyle,
|
||||||
placeholderTextColor: Color,
|
placeholderTextColor: Color,
|
||||||
placeholder: @Composable (() -> Unit)? = null,
|
placeholder: @Composable (() -> Unit)? = null,
|
||||||
trailingIcon: @Composable (() -> Unit)? = null,
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
@@ -147,8 +156,9 @@ private fun TextFieldDecorationBox(
|
|||||||
if (placeholder != null) {
|
if (placeholder != null) {
|
||||||
Box(modifier = Modifier.layoutId(PLACEHOLDER_ID), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.layoutId(PLACEHOLDER_ID), contentAlignment = Alignment.Center) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
|
LocalTextStyle provides textStyle.copy(color = placeholderTextColor),
|
||||||
LocalContentColor provides placeholderTextColor,
|
LocalContentColor provides placeholderTextColor,
|
||||||
content = placeholder
|
content = placeholder,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,40 +166,37 @@ private fun TextFieldDecorationBox(
|
|||||||
Box(modifier = Modifier.layoutId(TEXT_FIELD_ID), propagateMinConstraints = true) {
|
Box(modifier = Modifier.layoutId(TEXT_FIELD_ID), propagateMinConstraints = true) {
|
||||||
innerTextField()
|
innerTextField()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
) { measurables, incomingConstraints ->
|
) { measurables, incomingConstraints ->
|
||||||
// used to calculate the constraints for measuring elements that will be placed in a row
|
// used to calculate the constraints for measuring elements that will be placed in a row
|
||||||
var occupiedSpaceHorizontally = 0
|
var occupiedSpaceHorizontally = 0
|
||||||
|
val iconConstraints = incomingConstraints.copy(minWidth = 0, minHeight = 0)
|
||||||
val constraintsWithoutPadding = incomingConstraints
|
|
||||||
|
|
||||||
val iconsConstraints = constraintsWithoutPadding.copy(minWidth = 0, minHeight = 0)
|
|
||||||
|
|
||||||
// measure trailing icon
|
// measure trailing icon
|
||||||
val trailingPlaceable = measurables.find { it.layoutId == TRAILING_ID }
|
val trailingPlaceable = measurables.find { it.layoutId == TRAILING_ID }
|
||||||
?.measure(iconsConstraints)
|
?.measure(iconConstraints)
|
||||||
occupiedSpaceHorizontally += trailingPlaceable?.width ?: 0
|
occupiedSpaceHorizontally += trailingPlaceable?.width ?: 0
|
||||||
|
|
||||||
val textConstraints = constraintsWithoutPadding.offset(
|
val textFieldConstraints = incomingConstraints.offset(
|
||||||
horizontal = -occupiedSpaceHorizontally
|
horizontal = -occupiedSpaceHorizontally,
|
||||||
).copy(minHeight = 0)
|
).copy(minHeight = 0)
|
||||||
val textFieldPlaceable = measurables.first { it.layoutId == TEXT_FIELD_ID }.measure(textConstraints)
|
val textFieldPlaceable = measurables.first { it.layoutId == TEXT_FIELD_ID }.measure(textFieldConstraints)
|
||||||
|
|
||||||
// measure placeholder
|
// measure placeholder
|
||||||
val placeholderConstraints = textConstraints.copy(minWidth = 0)
|
val placeholderConstraints = textFieldConstraints.copy(minWidth = 0)
|
||||||
val placeholderPlaceable = measurables.find { it.layoutId == PLACEHOLDER_ID }?.measure(placeholderConstraints)
|
val placeholderPlaceable = measurables.find { it.layoutId == PLACEHOLDER_ID }?.measure(placeholderConstraints)
|
||||||
|
|
||||||
val width = calculateWidth(
|
val width = calculateWidth(
|
||||||
trailingPlaceable,
|
trailingPlaceable,
|
||||||
textFieldPlaceable,
|
textFieldPlaceable,
|
||||||
placeholderPlaceable,
|
placeholderPlaceable,
|
||||||
incomingConstraints
|
incomingConstraints,
|
||||||
)
|
)
|
||||||
val height = calculateHeight(
|
val height = calculateHeight(
|
||||||
trailingPlaceable,
|
trailingPlaceable,
|
||||||
textFieldPlaceable,
|
textFieldPlaceable,
|
||||||
placeholderPlaceable,
|
placeholderPlaceable,
|
||||||
incomingConstraints
|
incomingConstraints,
|
||||||
)
|
)
|
||||||
|
|
||||||
layout(width, height) {
|
layout(width, height) {
|
||||||
@@ -199,7 +206,6 @@ private fun TextFieldDecorationBox(
|
|||||||
trailingPlaceable,
|
trailingPlaceable,
|
||||||
textFieldPlaceable,
|
textFieldPlaceable,
|
||||||
placeholderPlaceable,
|
placeholderPlaceable,
|
||||||
this@Layout
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,7 +219,7 @@ private fun calculateWidth(
|
|||||||
): Int {
|
): Int {
|
||||||
val middleSection = maxOf(
|
val middleSection = maxOf(
|
||||||
textFieldPlaceable.width,
|
textFieldPlaceable.width,
|
||||||
placeholderPlaceable?.width ?: 0
|
placeholderPlaceable?.width ?: 0,
|
||||||
)
|
)
|
||||||
val wrappedWidth = middleSection + (trailingPlaceable?.width ?: 0)
|
val wrappedWidth = middleSection + (trailingPlaceable?.width ?: 0)
|
||||||
return max(wrappedWidth, constraints.minWidth)
|
return max(wrappedWidth, constraints.minWidth)
|
||||||
@@ -224,14 +230,12 @@ private fun calculateHeight(
|
|||||||
textFieldPlaceable: Placeable,
|
textFieldPlaceable: Placeable,
|
||||||
placeholderPlaceable: Placeable?,
|
placeholderPlaceable: Placeable?,
|
||||||
constraints: Constraints,
|
constraints: Constraints,
|
||||||
): Int {
|
): Int = maxOf(
|
||||||
return maxOf(
|
textFieldPlaceable.height,
|
||||||
textFieldPlaceable.height,
|
placeholderPlaceable?.height ?: 0,
|
||||||
placeholderPlaceable?.height ?: 0,
|
trailingPlaceable?.height ?: 0,
|
||||||
trailingPlaceable?.height ?: 0,
|
constraints.minHeight,
|
||||||
constraints.minHeight
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Placeable.PlacementScope.place(
|
private fun Placeable.PlacementScope.place(
|
||||||
height: Int,
|
height: Int,
|
||||||
@@ -239,28 +243,24 @@ private fun Placeable.PlacementScope.place(
|
|||||||
trailingPlaceable: Placeable?,
|
trailingPlaceable: Placeable?,
|
||||||
textFieldPlaceable: Placeable,
|
textFieldPlaceable: Placeable,
|
||||||
placeholderPlaceable: Placeable?,
|
placeholderPlaceable: Placeable?,
|
||||||
density: Density,
|
) {
|
||||||
) = with(density) {
|
|
||||||
// placed center vertically and to the end edge horizontally
|
// placed center vertically and to the end edge horizontally
|
||||||
trailingPlaceable?.placeRelative(
|
trailingPlaceable?.placeRelative(
|
||||||
width - trailingPlaceable.width,
|
width - trailingPlaceable.width,
|
||||||
Alignment.CenterVertically.align(trailingPlaceable.height, height)
|
Alignment.CenterVertically.align(trailingPlaceable.height, height),
|
||||||
)
|
)
|
||||||
|
|
||||||
// placed center vertically and after the leading icon horizontally if single line text field
|
// placed center vertically
|
||||||
// placed to the top with padding for multi line text field
|
|
||||||
textFieldPlaceable.placeRelative(
|
textFieldPlaceable.placeRelative(
|
||||||
0,
|
0,
|
||||||
Alignment.CenterVertically.align(textFieldPlaceable.height, height)
|
Alignment.CenterVertically.align(textFieldPlaceable.height, height),
|
||||||
)
|
)
|
||||||
|
|
||||||
// placed similar to the input text above
|
// placed similar to the input text above
|
||||||
placeholderPlaceable?.let {
|
placeholderPlaceable?.placeRelative(
|
||||||
it.placeRelative(
|
0,
|
||||||
0,
|
Alignment.CenterVertically.align(placeholderPlaceable.height, height),
|
||||||
Alignment.CenterVertically.align(it.height, height)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val PLACEHOLDER_ID = "Placeholder"
|
private const val PLACEHOLDER_ID = "Placeholder"
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import androidx.compose.ui.node.Ref
|
|||||||
import androidx.compose.ui.platform.debugInspectorInfo
|
import androidx.compose.ui.platform.debugInspectorInfo
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.isUnspecified
|
import androidx.compose.ui.unit.isUnspecified
|
||||||
import androidx.compose.ui.unit.takeOrElse
|
import androidx.compose.ui.unit.takeOrElse
|
||||||
import androidx.compose.ui.unit.toSize
|
import androidx.compose.ui.unit.toSize
|
||||||
@@ -49,14 +50,26 @@ fun Modifier.border(stroke: Stroke, shape: Shape): Modifier = when (stroke) {
|
|||||||
width = stroke.width,
|
width = stroke.width,
|
||||||
brush = stroke.brush,
|
brush = stroke.brush,
|
||||||
shape = shape,
|
shape = shape,
|
||||||
expand = stroke.expand
|
expand = stroke.expand,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Modifier.border(alignment: Stroke.Alignment, width: Dp, color: Color, shape: Shape = RectangleShape, expand: Dp = Dp.Unspecified) =
|
fun Modifier.border(
|
||||||
|
alignment: Stroke.Alignment,
|
||||||
|
width: Dp,
|
||||||
|
color: Color,
|
||||||
|
shape: Shape = RectangleShape,
|
||||||
|
expand: Dp = Dp.Unspecified,
|
||||||
|
) =
|
||||||
border(alignment, width, SolidColor(color), shape, expand)
|
border(alignment, width, SolidColor(color), shape, expand)
|
||||||
|
|
||||||
fun Modifier.border(alignment: Stroke.Alignment, width: Dp, brush: Brush, shape: Shape = RectangleShape, expand: Dp = Dp.Unspecified): Modifier =
|
fun Modifier.border(
|
||||||
|
alignment: Stroke.Alignment,
|
||||||
|
width: Dp,
|
||||||
|
brush: Brush,
|
||||||
|
shape: Shape = RectangleShape,
|
||||||
|
expand: Dp = Dp.Unspecified,
|
||||||
|
): Modifier =
|
||||||
if (alignment == Stroke.Alignment.Inside && expand.isUnspecified) {
|
if (alignment == Stroke.Alignment.Inside && expand.isUnspecified) {
|
||||||
// The compose native border modifier(androidx.compose.foundation.border) draws the border inside the shape,
|
// The compose native border modifier(androidx.compose.foundation.border) draws the border inside the shape,
|
||||||
// so we can just use that for getting a more native experience when drawing inside borders
|
// so we can just use that for getting a more native experience when drawing inside borders
|
||||||
@@ -81,11 +94,13 @@ private fun Modifier.drawBorderWithAlignment(
|
|||||||
val strokeWidthPx = max(
|
val strokeWidthPx = max(
|
||||||
min(
|
min(
|
||||||
if (width == Dp.Hairline) 1f else ceil(width.toPx()),
|
if (width == Dp.Hairline) 1f else ceil(width.toPx()),
|
||||||
ceil(size.minDimension / 2)
|
ceil(size.minDimension / 2),
|
||||||
),
|
),
|
||||||
1f
|
1f,
|
||||||
)
|
)
|
||||||
val expandWidthPx = expand.takeOrElse { Dp.Hairline }.toPx()
|
|
||||||
|
val expandWidthPx = expand.takeOrElse { 0.dp }.toPx()
|
||||||
|
|
||||||
when (val outline = shape.createOutline(size, layoutDirection, this)) {
|
when (val outline = shape.createOutline(size, layoutDirection, this)) {
|
||||||
is Outline.Rectangle -> {
|
is Outline.Rectangle -> {
|
||||||
when (shape) {
|
when (shape) {
|
||||||
@@ -95,19 +110,40 @@ private fun Modifier.drawBorderWithAlignment(
|
|||||||
Outline.Rounded(RoundRect(outline.rect)),
|
Outline.Rounded(RoundRect(outline.rect)),
|
||||||
brush,
|
brush,
|
||||||
strokeWidthPx,
|
strokeWidthPx,
|
||||||
expandWidthPx
|
expandWidthPx,
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> drawRectBorder(borderCacheRef, alignment, outline, brush, strokeWidthPx, expandWidthPx)
|
else -> drawRectBorder(
|
||||||
|
borderCacheRef,
|
||||||
|
alignment,
|
||||||
|
outline,
|
||||||
|
brush,
|
||||||
|
strokeWidthPx,
|
||||||
|
expandWidthPx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is Outline.Rounded -> drawRoundedBorder(borderCacheRef, alignment, outline, brush, strokeWidthPx, expandWidthPx)
|
is Outline.Rounded -> drawRoundedBorder(
|
||||||
|
borderCacheRef,
|
||||||
|
alignment,
|
||||||
|
outline,
|
||||||
|
brush,
|
||||||
|
strokeWidthPx,
|
||||||
|
expandWidthPx,
|
||||||
|
)
|
||||||
|
|
||||||
is Outline.Generic -> drawGenericBorder(borderCacheRef, alignment, outline, brush, strokeWidthPx, expandWidthPx)
|
is Outline.Generic -> drawGenericBorder(
|
||||||
|
borderCacheRef,
|
||||||
|
alignment,
|
||||||
|
outline,
|
||||||
|
brush,
|
||||||
|
strokeWidthPx,
|
||||||
|
expandWidthPx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
inspectorInfo = debugInspectorInfo {
|
inspectorInfo = debugInspectorInfo {
|
||||||
@@ -122,7 +158,7 @@ private fun Modifier.drawBorderWithAlignment(
|
|||||||
}
|
}
|
||||||
properties["shape"] = shape
|
properties["shape"] = shape
|
||||||
properties["expand"] = expand
|
properties["expand"] = expand
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
private class BorderCache(
|
private class BorderCache(
|
||||||
@@ -151,7 +187,7 @@ private class BorderCache(
|
|||||||
targetImageBitmap = ImageBitmap(
|
targetImageBitmap = ImageBitmap(
|
||||||
borderSize.width,
|
borderSize.width,
|
||||||
borderSize.height,
|
borderSize.height,
|
||||||
config = config
|
config = config,
|
||||||
).also {
|
).also {
|
||||||
imageBitmap = it
|
imageBitmap = it
|
||||||
}
|
}
|
||||||
@@ -166,12 +202,12 @@ private class BorderCache(
|
|||||||
this,
|
this,
|
||||||
layoutDirection,
|
layoutDirection,
|
||||||
targetCanvas,
|
targetCanvas,
|
||||||
drawSize
|
drawSize,
|
||||||
) {
|
) {
|
||||||
drawRect(
|
drawRect(
|
||||||
color = Color.Black,
|
color = Color.Black,
|
||||||
size = drawSize,
|
size = drawSize,
|
||||||
blendMode = BlendMode.Clear
|
blendMode = BlendMode.Clear,
|
||||||
)
|
)
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
@@ -219,26 +255,27 @@ private fun ContentDrawScope.drawRoundedBorder(
|
|||||||
strokeWidthPx: Float,
|
strokeWidthPx: Float,
|
||||||
expandWidthPx: Float,
|
expandWidthPx: Float,
|
||||||
) {
|
) {
|
||||||
val rrect = when (alignment) {
|
val roundRect = when (alignment) {
|
||||||
Stroke.Alignment.Inside -> outline.roundRect.inflate(expandWidthPx - strokeWidthPx / 2f)
|
Stroke.Alignment.Inside -> outline.roundRect.inflate(expandWidthPx - strokeWidthPx / 2f)
|
||||||
Stroke.Alignment.Center -> outline.roundRect.inflate(expandWidthPx)
|
Stroke.Alignment.Center -> outline.roundRect.inflate(expandWidthPx)
|
||||||
Stroke.Alignment.Outside -> outline.roundRect.inflate(expandWidthPx + strokeWidthPx / 2f)
|
Stroke.Alignment.Outside -> outline.roundRect.inflate(expandWidthPx + strokeWidthPx / 2f)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rrect.hasRightAngle()) {
|
if (roundRect.hasAtLeastOneNonRoundedCorner()) {
|
||||||
|
// Note: why do we need this? The Outline API can handle it just fine
|
||||||
val cache = borderCacheRef.obtain()
|
val cache = borderCacheRef.obtain()
|
||||||
val borderPath = cache.obtainPath().apply {
|
val borderPath = cache.obtainPath().apply {
|
||||||
reset()
|
reset()
|
||||||
fillType = PathFillType.EvenOdd
|
fillType = PathFillType.EvenOdd
|
||||||
addRoundRect(rrect.deflate(strokeWidthPx / 2f))
|
addRoundRect(roundRect.deflate(strokeWidthPx / 2f))
|
||||||
addRoundRect(rrect.inflate(strokeWidthPx / 2f))
|
addRoundRect(roundRect.inflate(strokeWidthPx / 2f))
|
||||||
}
|
}
|
||||||
drawPath(borderPath, brush)
|
drawPath(borderPath, brush)
|
||||||
} else {
|
} else {
|
||||||
drawOutline(
|
drawOutline(
|
||||||
outline = Outline.Rounded(rrect),
|
outline = Outline.Rounded(roundRect),
|
||||||
brush = brush,
|
brush = brush,
|
||||||
style = DrawScopeStroke(strokeWidthPx)
|
style = DrawScopeStroke(strokeWidthPx),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,10 +313,12 @@ private fun CacheDrawScope.drawGenericBorder(
|
|||||||
inner -> {
|
inner -> {
|
||||||
return@onDrawWithContent
|
return@onDrawWithContent
|
||||||
}
|
}
|
||||||
|
|
||||||
-inner -> {
|
-inner -> {
|
||||||
// Samply draw the outline when abs(outer) and abs(inner) are the same
|
// Samply draw the outline when abs(outer) and abs(inner) are the same
|
||||||
drawOutline(outline, brush, style = DrawScopeStroke(outer * 2f))
|
drawOutline(outline, brush, style = DrawScopeStroke(outer * 2f))
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
val config: ImageBitmapConfig
|
val config: ImageBitmapConfig
|
||||||
val colorFilter: ColorFilter?
|
val colorFilter: ColorFilter?
|
||||||
@@ -300,13 +339,13 @@ private fun CacheDrawScope.drawGenericBorder(
|
|||||||
val cacheImageBitmap: ImageBitmap
|
val cacheImageBitmap: ImageBitmap
|
||||||
val pathBoundsSize = IntSize(
|
val pathBoundsSize = IntSize(
|
||||||
ceil(pathBounds.width).toInt(),
|
ceil(pathBounds.width).toInt(),
|
||||||
ceil(pathBounds.height).toInt()
|
ceil(pathBounds.height).toInt(),
|
||||||
)
|
)
|
||||||
|
|
||||||
with(borderCache) {
|
with(borderCache) {
|
||||||
cacheImageBitmap = drawBorderCache(
|
cacheImageBitmap = drawBorderCache(
|
||||||
pathBoundsSize,
|
pathBoundsSize,
|
||||||
config
|
config,
|
||||||
) {
|
) {
|
||||||
translate(-pathBounds.left, -pathBounds.top) {
|
translate(-pathBounds.left, -pathBounds.top) {
|
||||||
if (inner < 0f && outer > 0f) {
|
if (inner < 0f && outer > 0f) {
|
||||||
@@ -317,7 +356,12 @@ private fun CacheDrawScope.drawGenericBorder(
|
|||||||
drawPath(path = outline.path, brush = brush, style = DrawScopeStroke(outer * 2f))
|
drawPath(path = outline.path, brush = brush, style = DrawScopeStroke(outer * 2f))
|
||||||
|
|
||||||
if (inner > 0f) {
|
if (inner > 0f) {
|
||||||
drawPath(path = outline.path, brush = brush, blendMode = BlendMode.Clear, style = DrawScopeStroke(inner * 2f))
|
drawPath(
|
||||||
|
path = outline.path,
|
||||||
|
brush = brush,
|
||||||
|
blendMode = BlendMode.Clear,
|
||||||
|
style = DrawScopeStroke(inner * 2f),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
drawPath(path = outline.path, brush = brush, blendMode = BlendMode.Clear)
|
drawPath(path = outline.path, brush = brush, blendMode = BlendMode.Clear)
|
||||||
@@ -327,7 +371,12 @@ private fun CacheDrawScope.drawGenericBorder(
|
|||||||
drawPath(path = outline.path, brush = brush, style = DrawScopeStroke(-inner * 2f))
|
drawPath(path = outline.path, brush = brush, style = DrawScopeStroke(-inner * 2f))
|
||||||
|
|
||||||
if (outer < 0f) {
|
if (outer < 0f) {
|
||||||
drawPath(path = outline.path, brush = brush, blendMode = BlendMode.Clear, style = DrawScopeStroke(-outer * 2f))
|
drawPath(
|
||||||
|
path = outline.path,
|
||||||
|
brush = brush,
|
||||||
|
blendMode = BlendMode.Clear,
|
||||||
|
style = DrawScopeStroke(-outer * 2f),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
drawPath(path = outerMaskPath, brush = brush, blendMode = BlendMode.Clear)
|
drawPath(path = outerMaskPath, brush = brush, blendMode = BlendMode.Clear)
|
||||||
@@ -11,7 +11,7 @@ internal fun RoundRect.inflate(delta: Float) = RoundRect(
|
|||||||
topLeftCornerRadius = CornerRadius(topLeftCornerRadius.x + delta, topLeftCornerRadius.y + delta),
|
topLeftCornerRadius = CornerRadius(topLeftCornerRadius.x + delta, topLeftCornerRadius.y + delta),
|
||||||
topRightCornerRadius = CornerRadius(topRightCornerRadius.x + delta, topRightCornerRadius.y + delta),
|
topRightCornerRadius = CornerRadius(topRightCornerRadius.x + delta, topRightCornerRadius.y + delta),
|
||||||
bottomLeftCornerRadius = CornerRadius(bottomLeftCornerRadius.x + delta, bottomLeftCornerRadius.y + delta),
|
bottomLeftCornerRadius = CornerRadius(bottomLeftCornerRadius.x + delta, bottomLeftCornerRadius.y + delta),
|
||||||
bottomRightCornerRadius = CornerRadius(bottomRightCornerRadius.x + delta, bottomRightCornerRadius.y + delta)
|
bottomRightCornerRadius = CornerRadius(bottomRightCornerRadius.x + delta, bottomRightCornerRadius.y + delta),
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun RoundRect.deflate(delta: Float) = RoundRect(
|
internal fun RoundRect.deflate(delta: Float) = RoundRect(
|
||||||
@@ -22,10 +22,10 @@ internal fun RoundRect.deflate(delta: Float) = RoundRect(
|
|||||||
topLeftCornerRadius = CornerRadius(topLeftCornerRadius.x - delta, topLeftCornerRadius.y - delta),
|
topLeftCornerRadius = CornerRadius(topLeftCornerRadius.x - delta, topLeftCornerRadius.y - delta),
|
||||||
topRightCornerRadius = CornerRadius(topRightCornerRadius.x - delta, topRightCornerRadius.y - delta),
|
topRightCornerRadius = CornerRadius(topRightCornerRadius.x - delta, topRightCornerRadius.y - delta),
|
||||||
bottomLeftCornerRadius = CornerRadius(bottomLeftCornerRadius.x - delta, bottomLeftCornerRadius.y - delta),
|
bottomLeftCornerRadius = CornerRadius(bottomLeftCornerRadius.x - delta, bottomLeftCornerRadius.y - delta),
|
||||||
bottomRightCornerRadius = CornerRadius(bottomRightCornerRadius.x - delta, bottomRightCornerRadius.y - delta)
|
bottomRightCornerRadius = CornerRadius(bottomRightCornerRadius.x - delta, bottomRightCornerRadius.y - delta),
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun RoundRect.hasRightAngle() =
|
internal fun RoundRect.hasAtLeastOneNonRoundedCorner() =
|
||||||
topLeftCornerRadius.x == 0f && topLeftCornerRadius.y == 0f ||
|
topLeftCornerRadius.x == 0f && topLeftCornerRadius.y == 0f ||
|
||||||
topRightCornerRadius.x == 0f && topRightCornerRadius.y == 0f ||
|
topRightCornerRadius.x == 0f && topRightCornerRadius.y == 0f ||
|
||||||
bottomLeftCornerRadius.x == 0f && bottomLeftCornerRadius.y == 0f ||
|
bottomLeftCornerRadius.x == 0f && bottomLeftCornerRadius.y == 0f ||
|
||||||
@@ -141,8 +141,8 @@ open class DefaultSelectableOnKeyEvent(
|
|||||||
selectableState.addElementsToSelection(
|
selectableState.addElementsToSelection(
|
||||||
listOf(
|
listOf(
|
||||||
currentIndex,
|
currentIndex,
|
||||||
prevIndex
|
prevIndex,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
selectableState.lastKeyEventUsedMouse = true
|
selectableState.lastKeyEventUsedMouse = true
|
||||||
}
|
}
|
||||||
@@ -170,8 +170,8 @@ open class DefaultSelectableOnKeyEvent(
|
|||||||
selectableState.addElementsToSelection(
|
selectableState.addElementsToSelection(
|
||||||
listOf(
|
listOf(
|
||||||
currentIndex,
|
currentIndex,
|
||||||
nextSelectableIndex
|
nextSelectableIndex,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
selectableState.lastKeyEventUsedMouse = true
|
selectableState.lastKeyEventUsedMouse = true
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ fun SelectableLazyColumn(
|
|||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
keyActions = keyActions.handleOnKeyEvent(rememberCoroutineScope()),
|
keyActions = keyActions.handleOnKeyEvent(rememberCoroutineScope()),
|
||||||
pointerHandlingScopedActions = pointerHandlingScopedActions,
|
pointerHandlingScopedActions = pointerHandlingScopedActions,
|
||||||
content = content
|
content = content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ internal fun BaseSelectableLazyColumn(
|
|||||||
reverseLayout = reverseLayout,
|
reverseLayout = reverseLayout,
|
||||||
verticalArrangement = verticalArrangement,
|
verticalArrangement = verticalArrangement,
|
||||||
horizontalAlignment = horizontalAlignment,
|
horizontalAlignment = horizontalAlignment,
|
||||||
flingBehavior = flingBehavior
|
flingBehavior = flingBehavior,
|
||||||
) {
|
) {
|
||||||
state.clearKeys()
|
state.clearKeys()
|
||||||
SelectableLazyListScopeContainer(state, pointerHandlingScopedActions).apply(content)
|
SelectableLazyListScopeContainer(state, pointerHandlingScopedActions).apply(content)
|
||||||
@@ -149,7 +149,7 @@ internal class SelectableLazyListScopeContainer(
|
|||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.then(if (selectable) Modifier.selectable(selectableKey, scope) else Modifier)
|
.then(if (selectable) Modifier.selectable(selectableKey, scope) else Modifier)
|
||||||
.then(if (focusable) Modifier.focusable(selectableKey as SelectableKey.Focusable, isFocused) else Modifier)
|
.then(if (focusable) Modifier.focusable(selectableKey as SelectableKey.Focusable, isFocused) else Modifier),
|
||||||
) {
|
) {
|
||||||
content(SelectableLazyItemScope(isSelected, isFocused))
|
content(SelectableLazyItemScope(isSelected, isFocused))
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ internal class SelectableLazyListScopeContainer(
|
|||||||
} else {
|
} else {
|
||||||
SelectableKey.NotFocusable(
|
SelectableKey.NotFocusable(
|
||||||
key(it),
|
key(it),
|
||||||
selectable(it)
|
selectable(it),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,11 +187,11 @@ internal class SelectableLazyListScopeContainer(
|
|||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.then(if (selectable(index)) Modifier.selectable(selectableKeys[index]) else Modifier)
|
.then(if (selectable(index)) Modifier.selectable(selectableKeys[index]) else Modifier)
|
||||||
.then(if (focusable(index)) Modifier.focusable(selectableKeys[index] as SelectableKey.Focusable, isFocused) else Modifier)
|
.then(if (focusable(index)) Modifier.focusable(selectableKeys[index] as SelectableKey.Focusable, isFocused) else Modifier),
|
||||||
) {
|
) {
|
||||||
itemContent(SelectableLazyItemScope(isFocused, isSelected), index)
|
itemContent(SelectableLazyItemScope(isFocused, isSelected), index)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +29,15 @@ import androidx.compose.ui.focus.onFocusChanged
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.jetbrains.jewel.CommonStateBitMask
|
||||||
|
import org.jetbrains.jewel.CommonStateBitMask.Active
|
||||||
|
import org.jetbrains.jewel.CommonStateBitMask.Enabled
|
||||||
|
import org.jetbrains.jewel.CommonStateBitMask.Focused
|
||||||
|
import org.jetbrains.jewel.CommonStateBitMask.Hovered
|
||||||
|
import org.jetbrains.jewel.CommonStateBitMask.Pressed
|
||||||
|
import org.jetbrains.jewel.CommonStateBitMask.Selected
|
||||||
|
import org.jetbrains.jewel.InteractiveComponentState
|
||||||
|
import org.jetbrains.jewel.SelectableComponentState
|
||||||
import org.jetbrains.jewel.foundation.lazy.SelectableLazyColumn
|
import org.jetbrains.jewel.foundation.lazy.SelectableLazyColumn
|
||||||
import org.jetbrains.jewel.foundation.lazy.SelectableLazyItemScope
|
import org.jetbrains.jewel.foundation.lazy.SelectableLazyItemScope
|
||||||
import org.jetbrains.jewel.foundation.utils.Log
|
import org.jetbrains.jewel.foundation.utils.Log
|
||||||
@@ -99,7 +108,7 @@ fun <T> BasicLazyTree(
|
|||||||
state = treeState.delegate,
|
state = treeState.delegate,
|
||||||
keyActions = keyActions,
|
keyActions = keyActions,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
pointerHandlingScopedActions = pointerEventScopedActions
|
pointerHandlingScopedActions = pointerEventScopedActions,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
count = flattenedTree.size,
|
count = flattenedTree.size,
|
||||||
@@ -107,13 +116,13 @@ fun <T> BasicLazyTree(
|
|||||||
val idPath = flattenedTree[it].idPath()
|
val idPath = flattenedTree[it].idPath()
|
||||||
idPath
|
idPath
|
||||||
},
|
},
|
||||||
contentType = { flattenedTree[it].data }
|
contentType = { flattenedTree[it].data },
|
||||||
) { itemIndex ->
|
) { itemIndex ->
|
||||||
val element = flattenedTree[itemIndex]
|
val element = flattenedTree[itemIndex]
|
||||||
val elementState = TreeElementState.of(
|
val elementState = TreeElementState.of(
|
||||||
focused = isFocused,
|
focused = isFocused,
|
||||||
selected = isSelected,
|
selected = isSelected,
|
||||||
expanded = (element as? Tree.Element.Node)?.let { it.idPath() in treeState.openNodes } ?: false
|
expanded = (element as? Tree.Element.Node)?.let { it.idPath() in treeState.openNodes } ?: false,
|
||||||
)
|
)
|
||||||
|
|
||||||
val backgroundShape by remember { mutableStateOf(RoundedCornerShape(elementBackgroundCornerSize)) }
|
val backgroundShape by remember { mutableStateOf(RoundedCornerShape(elementBackgroundCornerSize)) }
|
||||||
@@ -126,32 +135,32 @@ fun <T> BasicLazyTree(
|
|||||||
elementBackgroundSelectedFocused,
|
elementBackgroundSelectedFocused,
|
||||||
elementBackgroundFocused,
|
elementBackgroundFocused,
|
||||||
elementBackgroundSelected,
|
elementBackgroundSelected,
|
||||||
backgroundShape
|
backgroundShape,
|
||||||
)
|
)
|
||||||
.padding(elementContentPadding)
|
.padding(elementContentPadding)
|
||||||
.padding(start = (element.depth * indentSize.value).dp)
|
.padding(start = (element.depth * indentSize.value).dp)
|
||||||
.clickable(
|
.clickable(
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
indication = null
|
indication = null,
|
||||||
) {
|
) {
|
||||||
(pointerEventScopedActions as? DefaultTreeViewPointerEventAction)?.notifyItemClicked(
|
(pointerEventScopedActions as? DefaultTreeViewPointerEventAction)?.notifyItemClicked(
|
||||||
item = flattenedTree[itemIndex] as Tree.Element<T>,
|
item = flattenedTree[itemIndex] as Tree.Element<T>,
|
||||||
scope = scope,
|
scope = scope,
|
||||||
doubleClickTimeDelayMillis = platformDoubleClickDelay.inWholeMilliseconds,
|
doubleClickTimeDelayMillis = platformDoubleClickDelay.inWholeMilliseconds,
|
||||||
onElementClick = onElementClick,
|
onElementClick = onElementClick,
|
||||||
onElementDoubleClick = onElementDoubleClick
|
onElementDoubleClick = onElementDoubleClick,
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
if (element is Tree.Element.Node) {
|
if (element is Tree.Element.Node) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.clickable(
|
modifier = Modifier.clickable(
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
indication = null
|
indication = null,
|
||||||
) {
|
) {
|
||||||
treeState.toggleNode(element.idPath())
|
treeState.toggleNode(element.idPath())
|
||||||
onElementDoubleClick(element as Tree.Element<T>)
|
onElementDoubleClick(element as Tree.Element<T>)
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
chevronContent(elementState)
|
chevronContent(elementState)
|
||||||
}
|
}
|
||||||
@@ -177,54 +186,90 @@ private fun Modifier.elementBackground(
|
|||||||
state.isSelected && !state.isFocused -> selected
|
state.isSelected && !state.isFocused -> selected
|
||||||
else -> Color.Unspecified
|
else -> Color.Unspecified
|
||||||
},
|
},
|
||||||
shape = backgroundShape
|
shape = backgroundShape,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class TreeElementState(val state: ULong) {
|
value class TreeElementState(val state: ULong) : InteractiveComponentState, SelectableComponentState {
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
val isFocused: Boolean
|
override val isActive: Boolean
|
||||||
|
get() = state and Active != 0UL
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
override val isEnabled: Boolean
|
||||||
|
get() = state and Enabled != 0UL
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
override val isFocused: Boolean
|
||||||
get() = state and Focused != 0UL
|
get() = state and Focused != 0UL
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
val isSelected: Boolean
|
override val isPressed: Boolean
|
||||||
|
get() = state and Pressed != 0UL
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
override val isHovered: Boolean
|
||||||
|
get() = state and Hovered != 0UL
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
override val isSelected: Boolean
|
||||||
get() = state and Selected != 0UL
|
get() = state and Selected != 0UL
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
val isExpanded: Boolean
|
val isExpanded: Boolean
|
||||||
get() = state and Expanded != 0UL
|
get() = state and Expanded != 0UL
|
||||||
|
|
||||||
fun copy(
|
override fun toString(): String =
|
||||||
focused: Boolean = isFocused,
|
"${javaClass.simpleName}(enabled=$isEnabled, focused=$isFocused, expanded=$isExpanded, " +
|
||||||
selected: Boolean = isSelected,
|
"pressed=$isPressed, hovered=$isHovered, active=$isActive, selected=$isSelected)"
|
||||||
expanded: Boolean = isExpanded,
|
|
||||||
) = of(focused, selected, expanded)
|
|
||||||
|
|
||||||
override fun toString() =
|
fun copy(
|
||||||
"${javaClass.simpleName}(isFocused=$isFocused, isSelected=$isSelected, isExpanded=$isExpanded)"
|
enabled: Boolean = isEnabled,
|
||||||
|
focused: Boolean = isFocused,
|
||||||
|
expanded: Boolean = isExpanded,
|
||||||
|
pressed: Boolean = isPressed,
|
||||||
|
hovered: Boolean = isHovered,
|
||||||
|
active: Boolean = isActive,
|
||||||
|
selected: Boolean = isSelected,
|
||||||
|
) = of(
|
||||||
|
enabled = enabled,
|
||||||
|
focused = focused,
|
||||||
|
expanded = expanded,
|
||||||
|
pressed = pressed,
|
||||||
|
hovered = hovered,
|
||||||
|
active = active,
|
||||||
|
selected = selected,
|
||||||
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val Focused = 1UL shl 0
|
private const val EXPANDED_BIT_OFFSET = CommonStateBitMask.FIRST_AVAILABLE_OFFSET
|
||||||
private val Hovered = 1UL shl 1
|
|
||||||
private val Selected = 1UL shl 2
|
private val Expanded = 1UL shl EXPANDED_BIT_OFFSET
|
||||||
private val Expanded = 1UL shl 3
|
|
||||||
|
|
||||||
fun of(
|
fun of(
|
||||||
focused: Boolean,
|
enabled: Boolean = true,
|
||||||
selected: Boolean,
|
focused: Boolean = false,
|
||||||
expanded: Boolean,
|
expanded: Boolean = false,
|
||||||
|
hovered: Boolean = false,
|
||||||
|
pressed: Boolean = false,
|
||||||
|
active: Boolean = false,
|
||||||
|
selected: Boolean = false,
|
||||||
) = TreeElementState(
|
) = TreeElementState(
|
||||||
(if (focused) Focused else 0UL) or
|
(if (expanded) Expanded else 0UL) or
|
||||||
|
(if (enabled) Enabled else 0UL) or
|
||||||
|
(if (focused) Focused else 0UL) or
|
||||||
|
(if (pressed) Pressed else 0UL) or
|
||||||
|
(if (hovered) Hovered else 0UL) or
|
||||||
(if (selected) Selected else 0UL) or
|
(if (selected) Selected else 0UL) or
|
||||||
(if (expanded) Expanded else 0UL)
|
(if (active) Active else 0UL),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun flattenTree(
|
private fun flattenTree(
|
||||||
element: Tree.Element<*>,
|
element: Tree.Element<*>,
|
||||||
openNodes: SnapshotStateList<Any>,
|
openNodes: SnapshotStateList<Any>,
|
||||||
allNodes: SnapshotStateList<Any>,
|
allNodes: SnapshotStateList<Any>,
|
||||||
@@ -241,7 +286,7 @@ private suspend fun flattenTree(
|
|||||||
openNodes.removeAll(
|
openNodes.removeAll(
|
||||||
buildList {
|
buildList {
|
||||||
getAllSubNodes(element)
|
getAllSubNodes(element)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ class TreeBuilder<T> : TreeGeneratorScope<T> {
|
|||||||
parent = null,
|
parent = null,
|
||||||
previous = previous,
|
previous = previous,
|
||||||
next = null,
|
next = null,
|
||||||
id = elementBuilder.id
|
id = elementBuilder.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
is Element.Node -> Tree.Element.Node(
|
is Element.Node -> Tree.Element.Node(
|
||||||
@@ -60,7 +60,7 @@ class TreeBuilder<T> : TreeGeneratorScope<T> {
|
|||||||
childrenGenerator = { parent -> generateElements(parent, elementBuilder) },
|
childrenGenerator = { parent -> generateElements(parent, elementBuilder) },
|
||||||
previous = previous,
|
previous = previous,
|
||||||
next = null,
|
next = null,
|
||||||
id = elementBuilder.id
|
id = elementBuilder.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
elements.add(current)
|
elements.add(current)
|
||||||
@@ -87,7 +87,7 @@ private fun <T> generateElements(
|
|||||||
parent = parent,
|
parent = parent,
|
||||||
previous = previous,
|
previous = previous,
|
||||||
next = null,
|
next = null,
|
||||||
id = elementBuilder.id
|
id = elementBuilder.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
is TreeBuilder.Element.Node -> Tree.Element.Node(
|
is TreeBuilder.Element.Node -> Tree.Element.Node(
|
||||||
@@ -98,7 +98,7 @@ private fun <T> generateElements(
|
|||||||
childrenGenerator = { generateElements(it, elementBuilder) },
|
childrenGenerator = { generateElements(it, elementBuilder) },
|
||||||
previous = previous,
|
previous = previous,
|
||||||
next = null,
|
next = null,
|
||||||
id = elementBuilder.id
|
id = elementBuilder.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
previous.next = current
|
previous.next = current
|
||||||
@@ -116,6 +116,7 @@ private fun <T> evaluatePrevious(element: Tree.Element<T>): Tree.Element<T> = wh
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface TreeGeneratorScope<T> {
|
interface TreeGeneratorScope<T> {
|
||||||
|
|
||||||
fun addNode(
|
fun addNode(
|
||||||
data: T,
|
data: T,
|
||||||
id: Any = data.hashCode(),
|
id: Any = data.hashCode(),
|
||||||
@@ -56,9 +56,9 @@ open class DefaultTreeViewOnKeyEvent(
|
|||||||
treeState.addElementsToSelection(
|
treeState.addElementsToSelection(
|
||||||
listOf(
|
listOf(
|
||||||
currentIndex,
|
currentIndex,
|
||||||
prevIndex
|
prevIndex,
|
||||||
),
|
),
|
||||||
null
|
null,
|
||||||
)
|
)
|
||||||
treeState.lastKeyEventUsedMouse = true
|
treeState.lastKeyEventUsedMouse = true
|
||||||
}
|
}
|
||||||
@@ -88,9 +88,9 @@ open class DefaultTreeViewOnKeyEvent(
|
|||||||
treeState.addElementsToSelection(
|
treeState.addElementsToSelection(
|
||||||
listOf(
|
listOf(
|
||||||
currentIndex,
|
currentIndex,
|
||||||
nextFlattenIndex
|
nextFlattenIndex,
|
||||||
),
|
),
|
||||||
null
|
null,
|
||||||
)
|
)
|
||||||
treeState.lastKeyEventUsedMouse = true
|
treeState.lastKeyEventUsedMouse = true
|
||||||
}
|
}
|
||||||
@@ -13,8 +13,8 @@ fun rememberTreeState(selectionMode: SelectionMode = SelectionMode.Single) = rem
|
|||||||
TreeState(
|
TreeState(
|
||||||
SelectableLazyListState(
|
SelectableLazyListState(
|
||||||
LazyListState(),
|
LazyListState(),
|
||||||
selectionMode
|
selectionMode,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,8 +37,8 @@ interface ButtonColors {
|
|||||||
focused = backgroundFocused,
|
focused = backgroundFocused,
|
||||||
pressed = backgroundPressed,
|
pressed = backgroundPressed,
|
||||||
hovered = backgroundHovered,
|
hovered = backgroundHovered,
|
||||||
active = background
|
active = background,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val content: Color
|
val content: Color
|
||||||
@@ -55,15 +55,15 @@ interface ButtonColors {
|
|||||||
focused = contentFocused,
|
focused = contentFocused,
|
||||||
pressed = contentPressed,
|
pressed = contentPressed,
|
||||||
hovered = contentHovered,
|
hovered = contentHovered,
|
||||||
active = content
|
active = content,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val border: Color
|
val border: Brush
|
||||||
val borderDisabled: Color
|
val borderDisabled: Brush
|
||||||
val borderFocused: Color
|
val borderFocused: Brush
|
||||||
val borderPressed: Color
|
val borderPressed: Brush
|
||||||
val borderHovered: Color
|
val borderHovered: Brush
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun borderFor(state: ButtonState) = rememberUpdatedState(
|
fun borderFor(state: ButtonState) = rememberUpdatedState(
|
||||||
@@ -73,8 +73,8 @@ interface ButtonColors {
|
|||||||
focused = borderFocused,
|
focused = borderFocused,
|
||||||
pressed = borderPressed,
|
pressed = borderPressed,
|
||||||
hovered = borderHovered,
|
hovered = borderHovered,
|
||||||
active = border
|
active = border,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ interface CheckboxColors {
|
|||||||
!state.isEnabled -> checkboxBackgroundDisabled
|
!state.isEnabled -> checkboxBackgroundDisabled
|
||||||
state.toggleableState == ToggleableState.On -> checkboxBackgroundSelected
|
state.toggleableState == ToggleableState.On -> checkboxBackgroundSelected
|
||||||
else -> checkboxBackground
|
else -> checkboxBackground
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val content: Color
|
val content: Color
|
||||||
@@ -46,20 +46,7 @@ interface CheckboxColors {
|
|||||||
!state.isEnabled -> contentDisabled
|
!state.isEnabled -> contentDisabled
|
||||||
state.toggleableState == ToggleableState.On -> contentSelected
|
state.toggleableState == ToggleableState.On -> contentSelected
|
||||||
else -> content
|
else -> content
|
||||||
}
|
},
|
||||||
)
|
|
||||||
|
|
||||||
val checkboxBorder: Color
|
|
||||||
val checkboxBorderDisabled: Color
|
|
||||||
val checkboxBorderSelected: Color
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun borderFor(state: CheckboxState) = rememberUpdatedState(
|
|
||||||
when {
|
|
||||||
!state.isEnabled -> checkboxBorderDisabled
|
|
||||||
state.toggleableState == ToggleableState.On -> checkboxBorderSelected
|
|
||||||
else -> checkboxBorder
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import androidx.compose.runtime.staticCompositionLocalOf
|
|||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.DpSize
|
|
||||||
import org.jetbrains.jewel.ChipState
|
import org.jetbrains.jewel.ChipState
|
||||||
|
import org.jetbrains.jewel.IntelliJTheme
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
interface ChipStyle {
|
interface ChipStyle {
|
||||||
@@ -28,17 +28,31 @@ interface ChipColors {
|
|||||||
val backgroundFocused: Brush
|
val backgroundFocused: Brush
|
||||||
val backgroundPressed: Brush
|
val backgroundPressed: Brush
|
||||||
val backgroundHovered: Brush
|
val backgroundHovered: Brush
|
||||||
|
val backgroundSelected: Brush
|
||||||
|
val backgroundSelectedDisabled: Brush
|
||||||
|
val backgroundSelectedPressed: Brush
|
||||||
|
val backgroundSelectedFocused: Brush
|
||||||
|
val backgroundSelectedHovered: Brush
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun backgroundFor(state: ChipState) = rememberUpdatedState(
|
fun backgroundFor(state: ChipState) = rememberUpdatedState(
|
||||||
state.chooseValue(
|
if (state.isSelected) {
|
||||||
normal = background,
|
when {
|
||||||
disabled = backgroundDisabled,
|
!state.isEnabled -> backgroundSelectedDisabled
|
||||||
focused = backgroundFocused,
|
state.isPressed -> backgroundSelectedPressed
|
||||||
pressed = backgroundPressed,
|
state.isFocused -> backgroundSelectedFocused
|
||||||
hovered = backgroundHovered,
|
state.isHovered -> backgroundSelectedHovered
|
||||||
active = background
|
else -> backgroundSelected
|
||||||
)
|
}
|
||||||
|
} else {
|
||||||
|
when {
|
||||||
|
!state.isEnabled -> backgroundDisabled
|
||||||
|
state.isPressed -> backgroundPressed
|
||||||
|
state.isFocused -> backgroundFocused
|
||||||
|
state.isHovered -> backgroundHovered
|
||||||
|
else -> background
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val content: Color
|
val content: Color
|
||||||
@@ -46,17 +60,31 @@ interface ChipColors {
|
|||||||
val contentFocused: Color
|
val contentFocused: Color
|
||||||
val contentPressed: Color
|
val contentPressed: Color
|
||||||
val contentHovered: Color
|
val contentHovered: Color
|
||||||
|
val contentSelected: Color
|
||||||
|
val contentSelectedDisabled: Color
|
||||||
|
val contentSelectedPressed: Color
|
||||||
|
val contentSelectedFocused: Color
|
||||||
|
val contentSelectedHovered: Color
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun contentFor(state: ChipState) = rememberUpdatedState(
|
fun contentFor(state: ChipState) = rememberUpdatedState(
|
||||||
state.chooseValue(
|
if (state.isSelected) {
|
||||||
normal = content,
|
when {
|
||||||
disabled = contentDisabled,
|
!state.isEnabled -> contentSelectedDisabled
|
||||||
focused = contentFocused,
|
state.isPressed -> contentSelectedPressed
|
||||||
pressed = contentPressed,
|
state.isFocused -> contentSelectedFocused
|
||||||
hovered = contentHovered,
|
state.isHovered -> contentSelectedHovered
|
||||||
active = content
|
else -> contentSelected
|
||||||
)
|
}
|
||||||
|
} else {
|
||||||
|
when {
|
||||||
|
!state.isEnabled -> contentDisabled
|
||||||
|
state.isPressed -> contentPressed
|
||||||
|
state.isFocused -> contentFocused
|
||||||
|
state.isHovered -> contentHovered
|
||||||
|
else -> content
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val border: Color
|
val border: Color
|
||||||
@@ -64,27 +92,41 @@ interface ChipColors {
|
|||||||
val borderFocused: Color
|
val borderFocused: Color
|
||||||
val borderPressed: Color
|
val borderPressed: Color
|
||||||
val borderHovered: Color
|
val borderHovered: Color
|
||||||
|
val borderSelected: Color
|
||||||
|
val borderSelectedDisabled: Color
|
||||||
|
val borderSelectedPressed: Color
|
||||||
|
val borderSelectedFocused: Color
|
||||||
|
val borderSelectedHovered: Color
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun borderFor(state: ChipState) = rememberUpdatedState(
|
fun borderFor(state: ChipState) = rememberUpdatedState(
|
||||||
state.chooseValue(
|
if (state.isSelected) {
|
||||||
normal = border,
|
when {
|
||||||
disabled = borderDisabled,
|
!state.isEnabled -> borderSelectedDisabled
|
||||||
focused = borderFocused,
|
state.isPressed && !IntelliJTheme.isSwingCompatMode -> borderSelectedPressed
|
||||||
pressed = borderPressed,
|
state.isFocused -> borderSelectedFocused
|
||||||
hovered = borderHovered,
|
state.isHovered && !IntelliJTheme.isSwingCompatMode -> borderSelectedHovered
|
||||||
active = border
|
else -> borderSelected
|
||||||
)
|
}
|
||||||
|
} else {
|
||||||
|
when {
|
||||||
|
!state.isEnabled -> borderDisabled
|
||||||
|
state.isPressed && !IntelliJTheme.isSwingCompatMode -> borderPressed
|
||||||
|
state.isFocused -> borderFocused
|
||||||
|
state.isHovered && !IntelliJTheme.isSwingCompatMode -> borderHovered
|
||||||
|
else -> border
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
interface ChipMetrics {
|
interface ChipMetrics {
|
||||||
|
|
||||||
val minSize: DpSize
|
|
||||||
val cornerSize: CornerSize
|
val cornerSize: CornerSize
|
||||||
val padding: PaddingValues
|
val padding: PaddingValues
|
||||||
val borderWidth: Dp
|
val borderWidth: Dp
|
||||||
|
val borderWidthSelected: Dp
|
||||||
}
|
}
|
||||||
|
|
||||||
val LocalChipStyle = staticCompositionLocalOf<ChipStyle> {
|
val LocalChipStyle = staticCompositionLocalOf<ChipStyle> {
|
||||||
|
|||||||
@@ -31,21 +31,17 @@ interface DropdownColors {
|
|||||||
val backgroundFocused: Color
|
val backgroundFocused: Color
|
||||||
val backgroundPressed: Color
|
val backgroundPressed: Color
|
||||||
val backgroundHovered: Color
|
val backgroundHovered: Color
|
||||||
val backgroundWarning: Color
|
|
||||||
val backgroundError: Color
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun backgroundFor(state: DropdownState) = rememberUpdatedState(
|
fun backgroundFor(state: DropdownState) = rememberUpdatedState(
|
||||||
state.chooseValueWithOutline(
|
state.chooseValue(
|
||||||
normal = background,
|
normal = background,
|
||||||
disabled = backgroundDisabled,
|
disabled = backgroundDisabled,
|
||||||
focused = backgroundFocused,
|
focused = backgroundFocused,
|
||||||
pressed = backgroundPressed,
|
pressed = backgroundPressed,
|
||||||
hovered = backgroundHovered,
|
hovered = backgroundHovered,
|
||||||
warning = backgroundWarning,
|
active = background,
|
||||||
error = backgroundError,
|
),
|
||||||
active = background
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val content: Color
|
val content: Color
|
||||||
@@ -53,21 +49,17 @@ interface DropdownColors {
|
|||||||
val contentFocused: Color
|
val contentFocused: Color
|
||||||
val contentPressed: Color
|
val contentPressed: Color
|
||||||
val contentHovered: Color
|
val contentHovered: Color
|
||||||
val contentWarning: Color
|
|
||||||
val contentError: Color
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun contentFor(state: DropdownState) = rememberUpdatedState(
|
fun contentFor(state: DropdownState) = rememberUpdatedState(
|
||||||
state.chooseValueWithOutline(
|
state.chooseValue(
|
||||||
normal = content,
|
normal = content,
|
||||||
disabled = contentDisabled,
|
disabled = contentDisabled,
|
||||||
focused = contentFocused,
|
focused = contentFocused,
|
||||||
pressed = contentPressed,
|
pressed = contentPressed,
|
||||||
hovered = contentHovered,
|
hovered = contentHovered,
|
||||||
warning = contentWarning,
|
active = content,
|
||||||
error = contentError,
|
),
|
||||||
active = content
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val border: Color
|
val border: Color
|
||||||
@@ -75,21 +67,17 @@ interface DropdownColors {
|
|||||||
val borderFocused: Color
|
val borderFocused: Color
|
||||||
val borderPressed: Color
|
val borderPressed: Color
|
||||||
val borderHovered: Color
|
val borderHovered: Color
|
||||||
val borderWarning: Color
|
|
||||||
val borderError: Color
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun borderFor(state: DropdownState) = rememberUpdatedState(
|
fun borderFor(state: DropdownState) = rememberUpdatedState(
|
||||||
state.chooseValueWithOutline(
|
state.chooseValue(
|
||||||
normal = border,
|
normal = border,
|
||||||
disabled = borderDisabled,
|
disabled = borderDisabled,
|
||||||
focused = borderFocused,
|
focused = borderFocused,
|
||||||
pressed = borderPressed,
|
pressed = borderPressed,
|
||||||
hovered = borderHovered,
|
hovered = borderHovered,
|
||||||
warning = borderWarning,
|
active = border,
|
||||||
error = borderError,
|
),
|
||||||
active = border
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val iconTint: Color
|
val iconTint: Color
|
||||||
@@ -97,27 +85,24 @@ interface DropdownColors {
|
|||||||
val iconTintFocused: Color
|
val iconTintFocused: Color
|
||||||
val iconTintPressed: Color
|
val iconTintPressed: Color
|
||||||
val iconTintHovered: Color
|
val iconTintHovered: Color
|
||||||
val iconTintWarning: Color
|
|
||||||
val iconTintError: Color
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun iconTintFor(state: DropdownState) = rememberUpdatedState(
|
fun iconTintFor(state: DropdownState) = rememberUpdatedState(
|
||||||
state.chooseValueWithOutline(
|
state.chooseValue(
|
||||||
normal = iconTint,
|
normal = iconTint,
|
||||||
disabled = iconTintDisabled,
|
disabled = iconTintDisabled,
|
||||||
focused = iconTintFocused,
|
focused = iconTintFocused,
|
||||||
pressed = iconTintPressed,
|
pressed = iconTintPressed,
|
||||||
hovered = iconTintHovered,
|
hovered = iconTintHovered,
|
||||||
warning = iconTintWarning,
|
active = iconTint,
|
||||||
error = iconTintError,
|
),
|
||||||
active = iconTint
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
interface DropdownMetrics {
|
interface DropdownMetrics {
|
||||||
|
|
||||||
|
val arrowMinSize: DpSize
|
||||||
val minSize: DpSize
|
val minSize: DpSize
|
||||||
val cornerSize: CornerSize
|
val cornerSize: CornerSize
|
||||||
val contentPadding: PaddingValues
|
val contentPadding: PaddingValues
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ interface HorizontalProgressBarColors {
|
|||||||
|
|
||||||
val track: Color
|
val track: Color
|
||||||
val progress: Color
|
val progress: Color
|
||||||
|
val indeterminateBase: Color
|
||||||
val indeterminateHighlight: Color
|
val indeterminateHighlight: Color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
@@ -29,21 +28,17 @@ interface InputFieldColors {
|
|||||||
val backgroundFocused: Color
|
val backgroundFocused: Color
|
||||||
val backgroundPressed: Color
|
val backgroundPressed: Color
|
||||||
val backgroundHovered: Color
|
val backgroundHovered: Color
|
||||||
val backgroundWarning: Color
|
|
||||||
val backgroundError: Color
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun backgroundFor(state: InputFieldState) = rememberUpdatedState(
|
fun backgroundFor(state: InputFieldState) = rememberUpdatedState(
|
||||||
state.chooseValueWithOutline(
|
state.chooseValue(
|
||||||
normal = background,
|
normal = background,
|
||||||
disabled = backgroundDisabled,
|
disabled = backgroundDisabled,
|
||||||
focused = backgroundFocused,
|
focused = backgroundFocused,
|
||||||
pressed = backgroundPressed,
|
pressed = backgroundPressed,
|
||||||
hovered = backgroundHovered,
|
hovered = backgroundHovered,
|
||||||
warning = backgroundWarning,
|
active = background,
|
||||||
error = backgroundError,
|
),
|
||||||
active = background
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val content: Color
|
val content: Color
|
||||||
@@ -51,21 +46,17 @@ interface InputFieldColors {
|
|||||||
val contentFocused: Color
|
val contentFocused: Color
|
||||||
val contentPressed: Color
|
val contentPressed: Color
|
||||||
val contentHovered: Color
|
val contentHovered: Color
|
||||||
val contentWarning: Color
|
|
||||||
val contentError: Color
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun contentFor(state: InputFieldState) = rememberUpdatedState(
|
fun contentFor(state: InputFieldState) = rememberUpdatedState(
|
||||||
state.chooseValueWithOutline(
|
state.chooseValue(
|
||||||
normal = content,
|
normal = content,
|
||||||
disabled = contentDisabled,
|
disabled = contentDisabled,
|
||||||
focused = contentFocused,
|
focused = contentFocused,
|
||||||
pressed = contentPressed,
|
pressed = contentPressed,
|
||||||
hovered = contentHovered,
|
hovered = contentHovered,
|
||||||
warning = contentWarning,
|
active = content,
|
||||||
error = contentError,
|
),
|
||||||
active = content
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val border: Color
|
val border: Color
|
||||||
@@ -73,43 +64,35 @@ interface InputFieldColors {
|
|||||||
val borderFocused: Color
|
val borderFocused: Color
|
||||||
val borderPressed: Color
|
val borderPressed: Color
|
||||||
val borderHovered: Color
|
val borderHovered: Color
|
||||||
val borderWarning: Color
|
|
||||||
val borderError: Color
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun borderFor(state: InputFieldState) = rememberUpdatedState(
|
fun borderFor(state: InputFieldState) = rememberUpdatedState(
|
||||||
state.chooseValueWithOutline(
|
state.chooseValue(
|
||||||
normal = border,
|
normal = border,
|
||||||
disabled = borderDisabled,
|
disabled = borderDisabled,
|
||||||
focused = borderFocused,
|
focused = borderFocused,
|
||||||
pressed = borderPressed,
|
pressed = borderPressed,
|
||||||
hovered = borderHovered,
|
hovered = borderHovered,
|
||||||
warning = borderWarning,
|
active = border,
|
||||||
error = borderError,
|
),
|
||||||
active = border
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val cursor: Brush
|
val caret: Color
|
||||||
val cursorDisabled: Brush
|
val caretDisabled: Color
|
||||||
val cursorFocused: Brush
|
val caretFocused: Color
|
||||||
val cursorPressed: Brush
|
val caretPressed: Color
|
||||||
val cursorHovered: Brush
|
val caretHovered: Color
|
||||||
val cursorWarning: Brush
|
|
||||||
val cursorError: Brush
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun cursorFor(state: InputFieldState) = rememberUpdatedState(
|
fun caretFor(state: InputFieldState) = rememberUpdatedState(
|
||||||
state.chooseValueWithOutline(
|
state.chooseValue(
|
||||||
normal = cursor,
|
normal = caret,
|
||||||
disabled = cursorDisabled,
|
disabled = caretDisabled,
|
||||||
focused = cursorFocused,
|
focused = caretFocused,
|
||||||
pressed = cursorPressed,
|
pressed = caretPressed,
|
||||||
hovered = cursorHovered,
|
hovered = caretHovered,
|
||||||
warning = cursorWarning,
|
active = caret,
|
||||||
error = cursorError,
|
),
|
||||||
active = cursor
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ import androidx.compose.runtime.Stable
|
|||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.ResourceLoader
|
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import org.jetbrains.jewel.foundation.tree.TreeElementState
|
import org.jetbrains.jewel.foundation.tree.TreeElementState
|
||||||
import org.jetbrains.jewel.painterResource
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
interface LazyTreeStyle {
|
interface LazyTreeStyle {
|
||||||
@@ -33,11 +31,6 @@ interface LazyTreeColors {
|
|||||||
val contentSelected: Color
|
val contentSelected: Color
|
||||||
val contentSelectedFocused: Color
|
val contentSelectedFocused: Color
|
||||||
|
|
||||||
val chevronTint: Color
|
|
||||||
val chevronTintSelected: Color
|
|
||||||
val chevronTintFocused: Color
|
|
||||||
val chevronTintSelectedFocused: Color
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun contentFor(state: TreeElementState) = rememberUpdatedState(
|
fun contentFor(state: TreeElementState) = rememberUpdatedState(
|
||||||
when {
|
when {
|
||||||
@@ -45,17 +38,7 @@ interface LazyTreeColors {
|
|||||||
state.isFocused -> contentFocused
|
state.isFocused -> contentFocused
|
||||||
state.isSelected -> contentSelected
|
state.isSelected -> contentSelected
|
||||||
else -> content
|
else -> content
|
||||||
}
|
},
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun chevronTintFor(state: TreeElementState) = rememberUpdatedState(
|
|
||||||
when {
|
|
||||||
state.isSelected && state.isFocused -> chevronTintSelectedFocused
|
|
||||||
state.isFocused -> chevronTintFocused
|
|
||||||
state.isSelected -> chevronTintSelected
|
|
||||||
else -> chevronTint
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,10 +56,12 @@ interface LazyTreeMetrics {
|
|||||||
@Immutable
|
@Immutable
|
||||||
interface LazyTreeIcons {
|
interface LazyTreeIcons {
|
||||||
|
|
||||||
val nodeChevron: String
|
val nodeChevronCollapsed: StatefulPainterProvider<TreeElementState>
|
||||||
|
val nodeChevronExpanded: StatefulPainterProvider<TreeElementState>
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun nodeChevronPainter(resourceLoader: ResourceLoader) = painterResource(nodeChevron, resourceLoader)
|
fun nodeChevron(isExpanded: Boolean) =
|
||||||
|
if (isExpanded) nodeChevronExpanded else nodeChevronCollapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
val LocalLazyTreeStyle = staticCompositionLocalOf<LazyTreeStyle> {
|
val LocalLazyTreeStyle = staticCompositionLocalOf<LazyTreeStyle> {
|
||||||
|
|||||||
@@ -39,12 +39,9 @@ interface LinkColors {
|
|||||||
pressed = contentPressed,
|
pressed = contentPressed,
|
||||||
hovered = contentHovered,
|
hovered = contentHovered,
|
||||||
visited = contentVisited,
|
visited = contentVisited,
|
||||||
active = content
|
active = content,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val iconTint: Color
|
|
||||||
val iconTintDisabled: Color
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -81,8 +78,8 @@ interface LinkTextStyles {
|
|||||||
pressed = pressed,
|
pressed = pressed,
|
||||||
hovered = hovered,
|
hovered = hovered,
|
||||||
visited = visited,
|
visited = visited,
|
||||||
active = normal
|
active = normal,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import androidx.compose.runtime.Immutable
|
|||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
@@ -24,7 +23,7 @@ interface MenuStyle {
|
|||||||
@Immutable
|
@Immutable
|
||||||
interface MenuColors {
|
interface MenuColors {
|
||||||
|
|
||||||
val background: Brush
|
val background: Color
|
||||||
val border: Color
|
val border: Color
|
||||||
val shadow: Color
|
val shadow: Color
|
||||||
val itemColors: MenuItemColors
|
val itemColors: MenuItemColors
|
||||||
@@ -34,8 +33,7 @@ interface MenuColors {
|
|||||||
interface MenuMetrics {
|
interface MenuMetrics {
|
||||||
|
|
||||||
val cornerSize: CornerSize
|
val cornerSize: CornerSize
|
||||||
val margin: PaddingValues
|
val menuMargin: PaddingValues
|
||||||
val padding: PaddingValues
|
|
||||||
val contentPadding: PaddingValues
|
val contentPadding: PaddingValues
|
||||||
val offset: DpOffset
|
val offset: DpOffset
|
||||||
val shadowSize: Dp
|
val shadowSize: Dp
|
||||||
@@ -47,17 +45,17 @@ interface MenuMetrics {
|
|||||||
@Stable
|
@Stable
|
||||||
interface MenuItemMetrics {
|
interface MenuItemMetrics {
|
||||||
|
|
||||||
val cornerSize: CornerSize
|
val selectionCornerSize: CornerSize
|
||||||
val padding: PaddingValues
|
val outerPadding: PaddingValues
|
||||||
val contentPadding: PaddingValues
|
val contentPadding: PaddingValues
|
||||||
val separatorPadding: PaddingValues
|
val separatorPadding: PaddingValues
|
||||||
|
val separatorThickness: Dp
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
interface SubmenuMetrics {
|
interface SubmenuMetrics {
|
||||||
|
|
||||||
val offset: DpOffset
|
val offset: DpOffset
|
||||||
val itemPadding: PaddingValues
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -77,8 +75,8 @@ interface MenuItemColors {
|
|||||||
active = background,
|
active = background,
|
||||||
focused = backgroundFocused,
|
focused = backgroundFocused,
|
||||||
pressed = backgroundPressed,
|
pressed = backgroundPressed,
|
||||||
hovered = backgroundHovered
|
hovered = backgroundHovered,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val content: Color
|
val content: Color
|
||||||
@@ -95,8 +93,8 @@ interface MenuItemColors {
|
|||||||
focused = contentFocused,
|
focused = contentFocused,
|
||||||
pressed = contentPressed,
|
pressed = contentPressed,
|
||||||
hovered = contentHovered,
|
hovered = contentHovered,
|
||||||
active = content
|
active = content,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val iconTint: Color
|
val iconTint: Color
|
||||||
@@ -113,8 +111,8 @@ interface MenuItemColors {
|
|||||||
focused = iconTintFocused,
|
focused = iconTintFocused,
|
||||||
pressed = iconTintPressed,
|
pressed = iconTintPressed,
|
||||||
hovered = iconTintHovered,
|
hovered = iconTintHovered,
|
||||||
active = iconTint
|
active = iconTint,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val separator: Color
|
val separator: Color
|
||||||
@@ -122,7 +120,8 @@ interface MenuItemColors {
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
interface MenuIcons {
|
interface MenuIcons {
|
||||||
val submenuChevron: String
|
|
||||||
|
val submenuChevron: StatefulPainterProvider<MenuItemState>
|
||||||
}
|
}
|
||||||
|
|
||||||
val LocalMenuStyle = staticCompositionLocalOf<MenuStyle> {
|
val LocalMenuStyle = staticCompositionLocalOf<MenuStyle> {
|
||||||
|
|||||||
@@ -36,65 +36,7 @@ interface RadioButtonColors {
|
|||||||
state.isSelected -> contentSelected
|
state.isSelected -> contentSelected
|
||||||
state.isHovered -> contentHovered
|
state.isHovered -> contentHovered
|
||||||
else -> content
|
else -> content
|
||||||
}
|
},
|
||||||
)
|
|
||||||
|
|
||||||
val buttonColors: RadioButtonButtonColors
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO these should be used to tint the SVGs
|
|
||||||
@Immutable
|
|
||||||
interface RadioButtonButtonColors {
|
|
||||||
val fill: Color
|
|
||||||
val fillHovered: Color
|
|
||||||
val fillDisabled: Color
|
|
||||||
val fillSelected: Color
|
|
||||||
val fillSelectedHovered: Color
|
|
||||||
val fillSelectedDisabled: Color
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun fillFor(state: RadioButtonState) = rememberUpdatedState(
|
|
||||||
when {
|
|
||||||
!state.isEnabled && state.isSelected -> fillSelectedDisabled
|
|
||||||
!state.isEnabled -> fillDisabled
|
|
||||||
state.isSelected && state.isHovered -> fillSelectedHovered
|
|
||||||
state.isSelected -> fillSelected
|
|
||||||
state.isHovered -> fillHovered
|
|
||||||
else -> fill
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val border: Color
|
|
||||||
val borderHovered: Color
|
|
||||||
val borderDisabled: Color
|
|
||||||
val borderSelected: Color
|
|
||||||
val borderSelectedHovered: Color
|
|
||||||
val borderSelectedDisabled: Color
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun borderFor(state: RadioButtonState) = rememberUpdatedState(
|
|
||||||
when {
|
|
||||||
!state.isEnabled && state.isSelected -> borderSelectedDisabled
|
|
||||||
!state.isEnabled -> borderDisabled
|
|
||||||
state.isSelected && state.isHovered -> borderSelectedHovered
|
|
||||||
state.isSelected -> borderSelected
|
|
||||||
state.isHovered -> borderHovered
|
|
||||||
else -> border
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val markSelected: Color
|
|
||||||
val markSelectedHovered: Color
|
|
||||||
val markSelectedDisabled: Color
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun markFor(state: RadioButtonState) = rememberUpdatedState(
|
|
||||||
when {
|
|
||||||
!state.isSelected -> Color.Unspecified
|
|
||||||
!state.isEnabled -> markSelectedDisabled
|
|
||||||
state.isHovered -> markSelectedHovered
|
|
||||||
else -> markSelected
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import org.jetbrains.jewel.SvgLoader
|
|||||||
import org.jetbrains.jewel.painterResource
|
import org.jetbrains.jewel.painterResource
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class ResourcePainterProvider<T : InteractiveComponentState>(
|
open class ResourcePainterProvider<T : InteractiveComponentState>(
|
||||||
private val basePath: String,
|
private val basePath: String,
|
||||||
private val svgLoader: SvgLoader,
|
private val svgLoader: SvgLoader,
|
||||||
private val prefixTokensProvider: (state: T) -> String = { "" },
|
private val prefixTokensProvider: (state: T) -> String = { "" },
|
||||||
@@ -26,9 +26,9 @@ class ResourcePainterProvider<T : InteractiveComponentState>(
|
|||||||
override fun getPainter(state: T, resourceLoader: ResourceLoader): State<Painter> {
|
override fun getPainter(state: T, resourceLoader: ResourceLoader): State<Painter> {
|
||||||
val isSvg = basePath.endsWith(".svg", ignoreCase = true)
|
val isSvg = basePath.endsWith(".svg", ignoreCase = true)
|
||||||
val painter = if (isSvg) {
|
val painter = if (isSvg) {
|
||||||
svgLoader.loadSvgResource(basePath, resourceLoader) { patchPath(state, basePath) }
|
svgLoader.loadSvgResource(basePath, resourceLoader) { patchPath(state, basePath, resourceLoader) }
|
||||||
} else {
|
} else {
|
||||||
val patchedPath = patchPath(state, basePath)
|
val patchedPath = patchPath(state, basePath, resourceLoader)
|
||||||
painterResource(patchedPath, resourceLoader)
|
painterResource(patchedPath, resourceLoader)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class ResourcePainterProvider<T : InteractiveComponentState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun patchPath(state: T, basePath: String): String = buildString {
|
protected open fun patchPath(state: T, basePath: String, resourceLoader: ResourceLoader): String = buildString {
|
||||||
append(basePath.substringBeforeLast('/', ""))
|
append(basePath.substringBeforeLast('/', ""))
|
||||||
append('/')
|
append('/')
|
||||||
append(basePath.substringBeforeLast('.').substringAfterLast('/'))
|
append(basePath.substringBeforeLast('.').substringAfterLast('/'))
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ interface TabColors {
|
|||||||
focused = contentFocused,
|
focused = contentFocused,
|
||||||
pressed = contentPressed,
|
pressed = contentPressed,
|
||||||
hovered = contentHovered,
|
hovered = contentHovered,
|
||||||
active = content
|
active = content,
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -84,7 +84,7 @@ interface TabColors {
|
|||||||
state.isActive -> background
|
state.isActive -> background
|
||||||
state.isSelected -> backgroundSelected
|
state.isSelected -> backgroundSelected
|
||||||
else -> background
|
else -> background
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -97,9 +97,9 @@ interface TabColors {
|
|||||||
focused = underlineFocused,
|
focused = underlineFocused,
|
||||||
pressed = underlinePressed,
|
pressed = underlinePressed,
|
||||||
hovered = underlineHovered,
|
hovered = underlineHovered,
|
||||||
active = underline
|
active = underline,
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,9 +123,9 @@ interface TabContentAlpha {
|
|||||||
focused = iconFocused,
|
focused = iconFocused,
|
||||||
pressed = iconPressed,
|
pressed = iconPressed,
|
||||||
hovered = iconHovered,
|
hovered = iconHovered,
|
||||||
active = iconNormal
|
active = iconNormal,
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val labelNormal: Float
|
val labelNormal: Float
|
||||||
@@ -145,9 +145,9 @@ interface TabContentAlpha {
|
|||||||
focused = labelFocused,
|
focused = labelFocused,
|
||||||
pressed = labelPressed,
|
pressed = labelPressed,
|
||||||
hovered = labelHovered,
|
hovered = labelHovered,
|
||||||
active = labelNormal
|
active = labelNormal,
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,21 @@
|
|||||||
package org.jetbrains.jewel.styling
|
package org.jetbrains.jewel.styling
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import org.jetbrains.jewel.InputFieldState
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
interface TextAreaStyle : InputFieldStyle {
|
interface TextAreaStyle : InputFieldStyle {
|
||||||
|
|
||||||
override val colors: TextAreaColors
|
override val colors: TextAreaColors
|
||||||
override val metrics: InputFieldMetrics
|
override val metrics: InputFieldMetrics
|
||||||
val hintTextStyle: TextStyle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
interface TextAreaColors : InputFieldColors {
|
interface TextAreaColors : InputFieldColors {
|
||||||
|
|
||||||
val placeholder: Color
|
val placeholder: Color
|
||||||
val hintContent: Color
|
|
||||||
val hintContentDisabled: Color
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun hintContentFor(state: InputFieldState) = rememberUpdatedState(
|
|
||||||
if (state.isEnabled) hintContent else hintContentDisabled
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val LocalTextAreaStyle = staticCompositionLocalOf<TextAreaStyle> {
|
val LocalTextAreaStyle = staticCompositionLocalOf<TextAreaStyle> {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ object PaletteMapperFactory {
|
|||||||
"Checkbox.Focus.Thin.Selected" to "#ACCFF7",
|
"Checkbox.Focus.Thin.Selected" to "#ACCFF7",
|
||||||
"Checkbox.Focus.Thin.Selected.Dark" to "#466D94",
|
"Checkbox.Focus.Thin.Selected.Dark" to "#466D94",
|
||||||
"Tree.iconColor" to "#808080",
|
"Tree.iconColor" to "#808080",
|
||||||
"Tree.iconColor.Dark" to "#AFB1B3"
|
"Tree.iconColor.Dark" to "#AFB1B3",
|
||||||
)
|
)
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
|
|||||||
@@ -104,7 +104,9 @@ style:
|
|||||||
includeLineWrapping: false
|
includeLineWrapping: false
|
||||||
ForbiddenComment:
|
ForbiddenComment:
|
||||||
active: true
|
active: true
|
||||||
values: [ 'STOPSHIP' ]
|
comments:
|
||||||
|
- value: 'STOPSHIP'
|
||||||
|
reason: 'Forbidden STOPSHIP marker in comment, please address before shipping'
|
||||||
allowedPatterns: ''
|
allowedPatterns: ''
|
||||||
LoopWithTooManyJumpStatements:
|
LoopWithTooManyJumpStatements:
|
||||||
active: true
|
active: true
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
[versions]
|
[versions]
|
||||||
composeDesktop = "1.5.0-dev1136"
|
composeDesktop = "1.5.0-rc02"
|
||||||
coroutines = "1.6.4"
|
coroutines = "1.7.3"
|
||||||
detekt = "1.22.0"
|
detekt = "1.23.1"
|
||||||
idea = "232.8660.185"
|
idea = "232.8660.185"
|
||||||
ideaGradlePlugin = "1.13.0"
|
ideaGradlePlugin = "1.15.0"
|
||||||
javaSarif = "2.0"
|
javaSarif = "2.0"
|
||||||
kotlinSarif = "0.4.0"
|
kotlinSarif = "0.4.0"
|
||||||
jna = "5.10.0"
|
kotlin = "1.8.21"
|
||||||
kotlin = "1.8.20"
|
|
||||||
dokka = "1.8.20"
|
dokka = "1.8.20"
|
||||||
kotlinterGradlePlugin = "3.12.0"
|
kotlinterGradlePlugin = "3.16.0"
|
||||||
kotlinxSerialization = "1.5.1"
|
kotlinxSerialization = "1.5.1"
|
||||||
kotlinpoet = "1.14.2"
|
kotlinpoet = "1.14.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
compose-components-splitpane = { module = "org.jetbrains.compose.components:components-splitpane", version.ref = "composeDesktop" }
|
|
||||||
javaSarif = { module = "com.contrastsecurity:java-sarif", version.ref = "javaSarif" }
|
javaSarif = { module = "com.contrastsecurity:java-sarif", version.ref = "javaSarif" }
|
||||||
kotlinSarif = { module = "io.github.detekt.sarif4k:sarif4k", version.ref = "kotlinSarif" }
|
kotlinSarif = { module = "io.github.detekt.sarif4k:sarif4k", version.ref = "kotlinSarif" }
|
||||||
jna = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" }
|
|
||||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
||||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||||
ij-platform-ide-core = { module = "com.jetbrains.intellij.platform:ide-core", version.ref = "idea" }
|
ij-platform-ide-core = { module = "com.jetbrains.intellij.platform:ide-core", version.ref = "idea" }
|
||||||
|
|||||||
@@ -2,52 +2,42 @@
|
|||||||
|
|
||||||
package org.jetbrains.jewel.bridge
|
package org.jetbrains.jewel.bridge
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.platform.Typeface
|
|
||||||
import androidx.compose.ui.unit.TextUnit
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.intellij.ide.ui.LafManager
|
|
||||||
import com.intellij.ide.ui.LafManagerListener
|
import com.intellij.ide.ui.LafManagerListener
|
||||||
import com.intellij.openapi.application.Application
|
import com.intellij.openapi.application.Application
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
import com.intellij.util.messages.MessageBus
|
||||||
import com.intellij.util.messages.SimpleMessageBusConnection
|
import com.intellij.util.messages.SimpleMessageBusConnection
|
||||||
import com.intellij.util.messages.Topic
|
import com.intellij.util.messages.Topic
|
||||||
import com.intellij.util.ui.DirProvider
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.channels.ProducerScope
|
import kotlinx.coroutines.channels.ProducerScope
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.onStart
|
||||||
import org.jetbrains.skiko.toSkikoTypeface
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import javax.swing.UIManager
|
|
||||||
import java.awt.Color as AwtColor
|
|
||||||
|
|
||||||
internal val IntelliJApplication: Application
|
internal val IntelliJApplication: Application
|
||||||
get() = ApplicationManager.getApplication()
|
get() = ApplicationManager.getApplication()
|
||||||
|
|
||||||
internal val Application.intellijThemeFlow
|
private val Application.lookAndFeelFlow: Flow<Unit>
|
||||||
get() = lookAndFeelFlow.map { ObtainIntelliJTheme() }
|
get() = messageBus.flow(LafManagerListener.TOPIC) {
|
||||||
|
LafManagerListener { trySend(Unit) }
|
||||||
internal val Application.lookAndFeelFlow: Flow<LafManager>
|
|
||||||
get() = messageBusFlow(LafManagerListener.TOPIC, { LafManager.getInstance()!! }) {
|
|
||||||
LafManagerListener { trySend(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun <L : Any, K> Application.messageBusFlow(
|
internal fun Application.lookAndFeelChangedFlow(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
sharingStarted: SharingStarted = SharingStarted.Eagerly,
|
||||||
|
): Flow<Unit> =
|
||||||
|
lookAndFeelFlow
|
||||||
|
.onStart { emit(Unit) }
|
||||||
|
.shareIn(scope, sharingStarted, replay = 1)
|
||||||
|
|
||||||
|
internal fun <L : Any, K> MessageBus.flow(
|
||||||
topic: Topic<L>,
|
topic: Topic<L>,
|
||||||
initialValue: (suspend () -> K)? = null,
|
listener: ProducerScope<K>.() -> L,
|
||||||
@BuilderInference listener: ProducerScope<K>.() -> L
|
|
||||||
): Flow<K> = callbackFlow {
|
): Flow<K> = callbackFlow {
|
||||||
initialValue?.let { send(it()) }
|
val connection: SimpleMessageBusConnection = simpleConnect()
|
||||||
val connection: SimpleMessageBusConnection = messageBus.simpleConnect()
|
|
||||||
connection.subscribe(topic, listener())
|
connection.subscribe(topic, listener())
|
||||||
awaitClose { connection.disconnect() }
|
awaitClose { connection.disconnect() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package org.jetbrains.jewel.bridge
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import org.jetbrains.jewel.BorderColors
|
||||||
|
import org.jetbrains.jewel.GlobalColors
|
||||||
|
import org.jetbrains.jewel.OutlineColors
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
internal class BridgeGlobalColors(
|
||||||
|
override val borders: BorderColors,
|
||||||
|
override val outlines: OutlineColors,
|
||||||
|
@SwingLafKey("*.infoForeground") override val infoContent: Color,
|
||||||
|
) : GlobalColors {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as BridgeGlobalColors
|
||||||
|
|
||||||
|
if (borders != other.borders) return false
|
||||||
|
if (outlines != other.outlines) return false
|
||||||
|
if (infoContent != other.infoContent) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = borders.hashCode()
|
||||||
|
result = 31 * result + outlines.hashCode()
|
||||||
|
result = 31 * result + infoContent.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String =
|
||||||
|
"BridgeGlobalColors(borders=$borders, outlines=$outlines, infoContent=$infoContent)"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun readFromLaF() = BridgeGlobalColors(
|
||||||
|
borders = BridgeBorderColors.readFromLaF(),
|
||||||
|
outlines = BridgeOutlineColors.readFromLaF(),
|
||||||
|
infoContent = retrieveColorOrUnspecified("*.infoForeground"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
internal class BridgeBorderColors(
|
||||||
|
@SwingLafKey("Component.borderColor") override val normal: Color,
|
||||||
|
@SwingLafKey("Component.focusedBorderColor") override val focused: Color,
|
||||||
|
@SwingLafKey("*.disabledBorderColor") override val disabled: Color,
|
||||||
|
) : BorderColors {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as BridgeBorderColors
|
||||||
|
|
||||||
|
if (normal != other.normal) return false
|
||||||
|
if (focused != other.focused) return false
|
||||||
|
if (disabled != other.disabled) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = normal.hashCode()
|
||||||
|
result = 31 * result + focused.hashCode()
|
||||||
|
result = 31 * result + disabled.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String =
|
||||||
|
"BridgeBorderColors(normal=$normal, focused=$focused, disabled=$disabled)"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun readFromLaF() = BridgeBorderColors(
|
||||||
|
normal = retrieveColorOrUnspecified("Component.borderColor"),
|
||||||
|
focused = retrieveColorOrUnspecified("Component.focusedBorderColor"),
|
||||||
|
disabled = retrieveColorOrUnspecified("*.disabledBorderColor"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
internal class BridgeOutlineColors(
|
||||||
|
@SwingLafKey("*.focusColor") override val focused: Color,
|
||||||
|
@SwingLafKey("Component.warningFocusColor") override val focusedWarning: Color,
|
||||||
|
@SwingLafKey("Component.errorFocusColor") override val focusedError: Color,
|
||||||
|
@SwingLafKey("Component.inactiveWarningFocusColor") override val warning: Color,
|
||||||
|
@SwingLafKey("Component.inactiveErrorFocusColor") override val error: Color,
|
||||||
|
) : OutlineColors {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as BridgeOutlineColors
|
||||||
|
|
||||||
|
if (focused != other.focused) return false
|
||||||
|
if (focusedWarning != other.focusedWarning) return false
|
||||||
|
if (focusedError != other.focusedError) return false
|
||||||
|
if (warning != other.warning) return false
|
||||||
|
if (error != other.error) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = focused.hashCode()
|
||||||
|
result = 31 * result + focusedWarning.hashCode()
|
||||||
|
result = 31 * result + focusedError.hashCode()
|
||||||
|
result = 31 * result + warning.hashCode()
|
||||||
|
result = 31 * result + error.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String =
|
||||||
|
"BridgeOutlineColors(focused=$focused, focusedWarning=$focusedWarning, focusedError=$focusedError, " +
|
||||||
|
"warning=$warning, error=$error)"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun readFromLaF() = BridgeOutlineColors(
|
||||||
|
focused = retrieveColorOrUnspecified("*.focusColor"),
|
||||||
|
focusedWarning = retrieveColorOrUnspecified("Component.warningFocusColor"),
|
||||||
|
focusedError = retrieveColorOrUnspecified("Component.errorFocusColor"),
|
||||||
|
warning = retrieveColorOrUnspecified("Component.inactiveWarningFocusColor"),
|
||||||
|
error = retrieveColorOrUnspecified("Component.inactiveErrorFocusColor"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.jetbrains.jewel.bridge
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil
|
||||||
|
import org.jetbrains.jewel.GlobalMetrics
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
internal class BridgeGlobalMetrics(
|
||||||
|
override val outlineWidth: Dp,
|
||||||
|
) : GlobalMetrics {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as BridgeGlobalMetrics
|
||||||
|
|
||||||
|
if (outlineWidth != other.outlineWidth) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = outlineWidth.hashCode()
|
||||||
|
|
||||||
|
override fun toString(): String =
|
||||||
|
"BridgeGlobalMetrics(outlineWidth=$outlineWidth)"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun readFromLaF() = BridgeGlobalMetrics(
|
||||||
|
outlineWidth = DarculaUIUtil.BW.dp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.jetbrains.jewel.bridge
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import org.jetbrains.jewel.IntelliJThemeIconData
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
internal class BridgeIconData(
|
||||||
|
override val iconOverrides: Map<String, String>,
|
||||||
|
override val colorPalette: Map<String, String>,
|
||||||
|
) : IntelliJThemeIconData {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as BridgeIconData
|
||||||
|
|
||||||
|
if (iconOverrides != other.iconOverrides) return false
|
||||||
|
if (colorPalette != other.colorPalette) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = iconOverrides.hashCode()
|
||||||
|
result = 31 * result + colorPalette.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String =
|
||||||
|
"BridgeIconData(iconOverrides=$iconOverrides, colorPalette=$colorPalette)"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
// TODO retrieve icon data from Swing
|
||||||
|
fun readFromLaF() = BridgeIconData(emptyMap(), emptyMap())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package org.jetbrains.jewel.bridge
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.platform.Typeface
|
|
||||||
import androidx.compose.ui.unit.TextUnit
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
|
||||||
import com.intellij.ui.NewUI
|
|
||||||
import com.intellij.util.ui.DirProvider
|
|
||||||
import org.jetbrains.jewel.IntelliJTheme
|
|
||||||
import org.jetbrains.skiko.toSkikoTypeface
|
|
||||||
import javax.swing.UIManager
|
|
||||||
|
|
||||||
private val logger = Logger.getInstance("JewelBridge")
|
|
||||||
|
|
||||||
private val dirProvider = DirProvider()
|
|
||||||
|
|
||||||
internal fun ObtainIntelliJTheme(): IntelliJTheme {
|
|
||||||
val isIntUi = NewUI.isEnabled()
|
|
||||||
|
|
||||||
if (!isIntUi) {
|
|
||||||
// TODO return Darcula/IntelliJ Light theme instead
|
|
||||||
logger.warn("Darcula LaF (aka \"old UI\" are not supported yet, falling back to Int UI")
|
|
||||||
}
|
|
||||||
|
|
||||||
return bridgeIntUi()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun java.awt.Color.toComposeColor() = Color(
|
|
||||||
red = red,
|
|
||||||
green = green,
|
|
||||||
blue = blue,
|
|
||||||
alpha = alpha
|
|
||||||
)
|
|
||||||
|
|
||||||
internal fun retrieveColorOrNull(key: String) =
|
|
||||||
UIManager.getColor(key)?.toComposeColor()
|
|
||||||
|
|
||||||
internal fun retrieveColorOrUnspecified(key: String): Color {
|
|
||||||
val color = retrieveColorOrNull(key)
|
|
||||||
if (color == null) {
|
|
||||||
logger.warn("Color with key \"$key\" not found, fallback to 'Color.Unspecified'")
|
|
||||||
}
|
|
||||||
return color ?: Color.Unspecified
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun retrieveColorsOrUnspecified(vararg keys: String) = keys.map { retrieveColorOrUnspecified(it) }
|
|
||||||
|
|
||||||
// Based on LafIconLookup#findIcon
|
|
||||||
// TODO inject additional logic from ImageLoader#addFileNameVariant to support loading the right icon
|
|
||||||
// variants ([_dark], [@2x], [_stroke])
|
|
||||||
internal fun lookupIJSvgIcon(
|
|
||||||
name: String,
|
|
||||||
selected: Boolean = false,
|
|
||||||
focused: Boolean = false,
|
|
||||||
enabled: Boolean = true,
|
|
||||||
editable: Boolean = false,
|
|
||||||
pressed: Boolean = false
|
|
||||||
): @Composable () -> Painter {
|
|
||||||
var key = name
|
|
||||||
if (editable) {
|
|
||||||
key += "Editable"
|
|
||||||
}
|
|
||||||
if (selected) {
|
|
||||||
key += "Selected"
|
|
||||||
}
|
|
||||||
|
|
||||||
when {
|
|
||||||
pressed -> key += "Pressed"
|
|
||||||
focused -> key += "Focused"
|
|
||||||
!enabled -> key += "Disabled"
|
|
||||||
}
|
|
||||||
|
|
||||||
// for Mac blue theme and other LAFs use default directory icons
|
|
||||||
val dir = dirProvider.dir()
|
|
||||||
val path = "$dir$key.svg"
|
|
||||||
|
|
||||||
return {
|
|
||||||
rememberSvgResource(path.removePrefix("/"), dirProvider.javaClass.classLoader)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun retrieveIntAsDp(key: String) = UIManager.getInt(key).dp
|
|
||||||
|
|
||||||
internal fun retrieveInsetsAsPaddingValues(key: String) =
|
|
||||||
UIManager.getInsets(key)
|
|
||||||
.let { PaddingValues(it.left.dp, it.top.dp, it.right.dp, it.bottom.dp) }
|
|
||||||
|
|
||||||
internal suspend fun retrieveFont(
|
|
||||||
key: String,
|
|
||||||
color: Color = Color.Unspecified,
|
|
||||||
lineHeight: TextUnit = TextUnit.Unspecified
|
|
||||||
): TextStyle {
|
|
||||||
val font = UIManager.getFont(key) ?: error("Font with key \"$key\" not found, fallback to 'Typeface.makeDefault()'")
|
|
||||||
return with(font) {
|
|
||||||
val typeface = toSkikoTypeface() ?: org.jetbrains.skia.Typeface.makeDefault().also {
|
|
||||||
logger.warn("Unable to convert font ${font.fontName} into a Skiko typeface, fallback to 'Typeface.makeDefault()'")
|
|
||||||
}
|
|
||||||
TextStyle(
|
|
||||||
color = color,
|
|
||||||
fontSize = size.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontFamily = FontFamily(Typeface(typeface)),
|
|
||||||
// todo textDecoration might be defined in the awt theme
|
|
||||||
lineHeight = lineHeight
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package org.jetbrains.jewel.bridge
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
|
import org.jetbrains.jewel.themes.intui.core.IntUiThemeColorPalette
|
||||||
|
|
||||||
|
private val logger = Logger.getInstance("BridgeThemeColorPalette")
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class BridgeThemeColorPalette(
|
||||||
|
private val grey: List<Color>,
|
||||||
|
private val blue: List<Color>,
|
||||||
|
private val green: List<Color>,
|
||||||
|
private val red: List<Color>,
|
||||||
|
private val yellow: List<Color>,
|
||||||
|
private val orange: List<Color>,
|
||||||
|
private val purple: List<Color>,
|
||||||
|
private val teal: List<Color>,
|
||||||
|
) : IntUiThemeColorPalette {
|
||||||
|
|
||||||
|
override fun grey(): List<Color> = grey
|
||||||
|
|
||||||
|
override fun grey(index: Int): Color = grey[index - 1]
|
||||||
|
|
||||||
|
override fun blue(): List<Color> = blue
|
||||||
|
|
||||||
|
override fun blue(index: Int): Color = blue[index - 1]
|
||||||
|
|
||||||
|
override fun green(): List<Color> = green
|
||||||
|
|
||||||
|
override fun green(index: Int): Color = green[index - 1]
|
||||||
|
|
||||||
|
override fun red(): List<Color> = red
|
||||||
|
|
||||||
|
override fun red(index: Int): Color = red[index - 1]
|
||||||
|
|
||||||
|
override fun yellow(): List<Color> = yellow
|
||||||
|
|
||||||
|
override fun yellow(index: Int): Color = yellow[index - 1]
|
||||||
|
|
||||||
|
override fun orange(): List<Color> = orange
|
||||||
|
|
||||||
|
override fun orange(index: Int): Color = orange[index - 1]
|
||||||
|
|
||||||
|
override fun purple(): List<Color> = purple
|
||||||
|
|
||||||
|
override fun purple(index: Int): Color = purple[index - 1]
|
||||||
|
|
||||||
|
override fun teal(): List<Color> = teal
|
||||||
|
|
||||||
|
override fun teal(index: Int): Color = teal[index - 1]
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun readFromLaF() = BridgeThemeColorPalette(
|
||||||
|
grey = readPaletteColors("Grey"),
|
||||||
|
blue = readPaletteColors("Blue"),
|
||||||
|
green = readPaletteColors("Green"),
|
||||||
|
red = readPaletteColors("Red"),
|
||||||
|
yellow = readPaletteColors("Yellow"),
|
||||||
|
orange = readPaletteColors("Orange"),
|
||||||
|
purple = readPaletteColors("Purple"),
|
||||||
|
teal = readPaletteColors("Teal"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun readPaletteColors(colorName: String): List<Color> {
|
||||||
|
val defaults = uiDefaults
|
||||||
|
val allKeys = defaults.keys
|
||||||
|
val colorNameKeyPrefix = "ColorPalette.$colorName"
|
||||||
|
val colorNameKeyPrefixLength = colorNameKeyPrefix.length
|
||||||
|
|
||||||
|
val lastColorIndex = allKeys.asSequence()
|
||||||
|
.filterIsInstance(String::class.java)
|
||||||
|
.filter { it.startsWith(colorNameKeyPrefix) }
|
||||||
|
.mapNotNull {
|
||||||
|
val afterName = it.substring(colorNameKeyPrefixLength)
|
||||||
|
afterName.toIntOrNull()
|
||||||
|
}
|
||||||
|
.maxOrNull() ?: return emptyList()
|
||||||
|
|
||||||
|
return buildList {
|
||||||
|
for (i in 1..lastColorIndex) {
|
||||||
|
val value = defaults["$colorNameKeyPrefix$i"] as? java.awt.Color
|
||||||
|
if (value == null) {
|
||||||
|
logger.error("Unable to find color value for palette key '$colorNameKeyPrefix$i'")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
add(value.toComposeColor())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package org.jetbrains.jewel.bridge
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.shape.CornerSize
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.TileMode
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.platform.Typeface
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.TextUnit
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
|
import com.intellij.ui.JBColor
|
||||||
|
import com.intellij.util.ui.JBValue
|
||||||
|
import org.jetbrains.jewel.IntelliJThemeIconData
|
||||||
|
import org.jetbrains.jewel.InteractiveComponentState
|
||||||
|
import org.jetbrains.jewel.SvgLoader
|
||||||
|
import org.jetbrains.jewel.styling.StatefulPainterProvider
|
||||||
|
import org.jetbrains.skiko.DependsOnJBR
|
||||||
|
import org.jetbrains.skiko.awt.font.AwtFontManager
|
||||||
|
import org.jetbrains.skiko.toSkikoTypefaceOrNull
|
||||||
|
import javax.swing.UIManager
|
||||||
|
|
||||||
|
private val logger = Logger.getInstance("JewelBridge")
|
||||||
|
|
||||||
|
fun java.awt.Color.toComposeColor() = Color(
|
||||||
|
red = red,
|
||||||
|
green = green,
|
||||||
|
blue = blue,
|
||||||
|
alpha = alpha,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun java.awt.Color?.toComposeColorOrUnspecified() = this?.toComposeColor() ?: Color.Unspecified
|
||||||
|
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
|
internal fun retrieveColorOrNull(key: String) =
|
||||||
|
JBColor.namedColor(key)
|
||||||
|
.takeUnless { it.name == "NAMED_COLOR_FALLBACK_MARKER" }
|
||||||
|
?.toComposeColor()
|
||||||
|
|
||||||
|
internal fun retrieveColorOrUnspecified(key: String): Color {
|
||||||
|
val color = retrieveColorOrNull(key)
|
||||||
|
if (color == null) {
|
||||||
|
logger.warn("Color with key \"$key\" not found, fallback to 'Color.Unspecified'")
|
||||||
|
}
|
||||||
|
return color ?: Color.Unspecified
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun retrieveColorsOrUnspecified(vararg keys: String) = keys.map { retrieveColorOrUnspecified(it) }
|
||||||
|
|
||||||
|
internal fun List<Color>.createVerticalBrush(
|
||||||
|
startY: Float = 0.0f,
|
||||||
|
endY: Float = Float.POSITIVE_INFINITY,
|
||||||
|
tileMode: TileMode = TileMode.Clamp,
|
||||||
|
): Brush {
|
||||||
|
if (isEmpty()) return SolidColor(Color.Transparent)
|
||||||
|
if (size == 1) return SolidColor(first())
|
||||||
|
|
||||||
|
// Optimization: use a cheaper SolidColor if all colors are identical
|
||||||
|
if (all { it == first() }) SolidColor(first())
|
||||||
|
|
||||||
|
return Brush.verticalGradient(this, startY, endY, tileMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun retrieveIntAsDp(key: String): Dp {
|
||||||
|
val rawValue = UIManager.get(key)
|
||||||
|
if (rawValue is Int) rawValue.dp
|
||||||
|
|
||||||
|
keyNotFound(key, "Int")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun retrieveIntAsDpOrUnspecified(key: String) =
|
||||||
|
try {
|
||||||
|
retrieveIntAsDp(key)
|
||||||
|
} catch (ignored: JewelBridgeException) {
|
||||||
|
Dp.Unspecified
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun retrieveInsetsAsPaddingValues(key: String) =
|
||||||
|
UIManager.getInsets(key)
|
||||||
|
?.let { PaddingValues(it.left.dp, it.top.dp, it.right.dp, it.bottom.dp) }
|
||||||
|
?: keyNotFound(key, "Insets")
|
||||||
|
|
||||||
|
internal fun retrieveArcAsCornerSize(key: String) =
|
||||||
|
CornerSize(retrieveIntAsDp(key) / 2)
|
||||||
|
|
||||||
|
internal fun retrieveArcAsCornerSizeWithFallbacks(vararg keys: String): CornerSize {
|
||||||
|
for (key in keys) {
|
||||||
|
val rawValue = UIManager.get(key)
|
||||||
|
if (rawValue is Int) {
|
||||||
|
val cornerSize = rawValue.dp
|
||||||
|
|
||||||
|
// Swing uses arcs, which are a diameter length, but we need a radius
|
||||||
|
return CornerSize(cornerSize / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keysNotFound(keys.toList(), "Int")
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DependsOnJBR::class)
|
||||||
|
private val awtFontManager = AwtFontManager()
|
||||||
|
|
||||||
|
internal suspend fun retrieveTextStyle(fontKey: String, colorKey: String? = null): TextStyle {
|
||||||
|
val baseColor = colorKey?.let { retrieveColorOrUnspecified(colorKey) } ?: Color.Unspecified
|
||||||
|
return retrieveTextStyle(fontKey, color = baseColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DependsOnJBR::class)
|
||||||
|
internal suspend fun retrieveTextStyle(
|
||||||
|
key: String,
|
||||||
|
color: Color = Color.Unspecified,
|
||||||
|
lineHeight: TextUnit = TextUnit.Unspecified,
|
||||||
|
): TextStyle {
|
||||||
|
val font = UIManager.getFont(key) ?: keyNotFound(key, "Font")
|
||||||
|
|
||||||
|
return with(font) {
|
||||||
|
val typeface = toSkikoTypefaceOrNull(awtFontManager)
|
||||||
|
?: org.jetbrains.skia.Typeface.makeDefault()
|
||||||
|
.also {
|
||||||
|
logger.warn(
|
||||||
|
"Unable to convert font ${font.fontName} into a Skiko typeface, " +
|
||||||
|
"fallback to 'Typeface.makeDefault()'",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle(
|
||||||
|
color = color,
|
||||||
|
fontSize = size.sp,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontFamily = FontFamily(Typeface(typeface)),
|
||||||
|
// todo textDecoration might be defined in the awt theme
|
||||||
|
lineHeight = lineHeight,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val JBValue.dp
|
||||||
|
get() = unscaled.dp
|
||||||
|
|
||||||
|
internal fun TextStyle.derive(sizeDelta: Float, weight: FontWeight? = fontWeight, color: Color = toSpanStyle().color) =
|
||||||
|
copy(fontSize = fontSize - sizeDelta, fontWeight = weight, color = color)
|
||||||
|
|
||||||
|
internal operator fun TextUnit.minus(delta: Float) = plus(-delta)
|
||||||
|
|
||||||
|
internal operator fun TextUnit.plus(delta: Float) =
|
||||||
|
when {
|
||||||
|
isSp -> TextUnit(value + delta, type)
|
||||||
|
isEm -> TextUnit(value + delta, type)
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun <T : InteractiveComponentState> retrieveIcon(
|
||||||
|
baseIconPath: String,
|
||||||
|
iconData: IntelliJThemeIconData,
|
||||||
|
svgLoader: SvgLoader,
|
||||||
|
prefixTokensProvider: (state: T) -> String = { "" },
|
||||||
|
suffixTokensProvider: (state: T) -> String = { "" },
|
||||||
|
): StatefulPainterProvider<T> = IntelliJResourcePainterProvider(
|
||||||
|
basePath = iconData.iconOverrides[baseIconPath] ?: baseIconPath,
|
||||||
|
svgLoader,
|
||||||
|
prefixTokensProvider,
|
||||||
|
suffixTokensProvider,
|
||||||
|
)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.jetbrains.jewel.bridge
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.awt.ComposePanel
|
||||||
|
import com.intellij.openapi.wm.ToolWindow
|
||||||
|
|
||||||
|
fun ToolWindow.addComposePanel(
|
||||||
|
tabDisplayName: String,
|
||||||
|
isLockable: Boolean = true,
|
||||||
|
content: @Composable ComposePanel.() -> Unit,
|
||||||
|
) = ComposePanel {
|
||||||
|
content()
|
||||||
|
}.also { contentManager.addContent(contentManager.factory.createContent(it, tabDisplayName, isLockable)) }
|
||||||
|
|
||||||
|
internal fun ComposePanel(
|
||||||
|
height: Int = 800,
|
||||||
|
width: Int = 800,
|
||||||
|
y: Int = 0,
|
||||||
|
x: Int = 0,
|
||||||
|
content: @Composable ComposePanel.() -> Unit,
|
||||||
|
): ComposePanel {
|
||||||
|
val panel = ComposePanel()
|
||||||
|
panel.setBounds(x, y, width, height)
|
||||||
|
panel.setContent {
|
||||||
|
panel.content()
|
||||||
|
}
|
||||||
|
return panel
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,43 +0,0 @@
|
|||||||
package org.jetbrains.jewel.bridge
|
|
||||||
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import org.jetbrains.jewel.ButtonDefaults
|
|
||||||
import org.jetbrains.jewel.CheckboxDefaults
|
|
||||||
import org.jetbrains.jewel.ChipDefaults
|
|
||||||
import org.jetbrains.jewel.DropdownDefaults
|
|
||||||
import org.jetbrains.jewel.GroupHeaderDefaults
|
|
||||||
import org.jetbrains.jewel.IntelliJColors
|
|
||||||
import org.jetbrains.jewel.LabelledTextFieldDefaults
|
|
||||||
import org.jetbrains.jewel.LinkDefaults
|
|
||||||
import org.jetbrains.jewel.MenuDefaults
|
|
||||||
import org.jetbrains.jewel.ThemeColors
|
|
||||||
import org.jetbrains.jewel.ProgressBarDefaults
|
|
||||||
import org.jetbrains.jewel.RadioButtonDefaults
|
|
||||||
import org.jetbrains.jewel.ScrollThumbDefaults
|
|
||||||
import org.jetbrains.jewel.TextAreaDefaults
|
|
||||||
import org.jetbrains.jewel.TextFieldDefaults
|
|
||||||
import org.jetbrains.jewel.TreeDefaults
|
|
||||||
import org.jetbrains.jewel.themes.intui.core.IntUiColorPalette
|
|
||||||
import org.jetbrains.jewel.themes.intui.standalone.IntUiTheme
|
|
||||||
|
|
||||||
class IntUiBridgeTheme internal constructor(
|
|
||||||
override val isDark: Boolean,
|
|
||||||
palette: IntUiColorPalette,
|
|
||||||
override val colors: IntelliJColors,
|
|
||||||
val themeColors: ThemeColors,
|
|
||||||
override val buttonDefaults: ButtonDefaults,
|
|
||||||
override val checkboxDefaults: CheckboxDefaults,
|
|
||||||
override val groupHeaderDefaults: GroupHeaderDefaults,
|
|
||||||
override val linkDefaults: LinkDefaults,
|
|
||||||
override val textFieldDefaults: TextFieldDefaults,
|
|
||||||
override val labelledTextFieldDefaults: LabelledTextFieldDefaults,
|
|
||||||
override val textAreaDefaults: TextAreaDefaults,
|
|
||||||
override val radioButtonDefaults: RadioButtonDefaults,
|
|
||||||
override val dropdownDefaults: DropdownDefaults,
|
|
||||||
override val contextMenuDefaults: MenuDefaults,
|
|
||||||
override val defaultTextStyle: TextStyle,
|
|
||||||
override val treeDefaults: TreeDefaults,
|
|
||||||
override val chipDefaults: ChipDefaults,
|
|
||||||
override val scrollThumbDefaults: ScrollThumbDefaults,
|
|
||||||
override val progressBarDefaults: ProgressBarDefaults,
|
|
||||||
): IntUiTheme(palette)
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package org.jetbrains.jewel.bridge
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.ResourceLoader
|
||||||
|
import com.intellij.ide.ui.IconMapLoader
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
|
import com.intellij.openapi.diagnostic.thisLogger
|
||||||
|
import org.jetbrains.jewel.InteractiveComponentState
|
||||||
|
import org.jetbrains.jewel.JewelResourceLoader
|
||||||
|
import org.jetbrains.jewel.SvgLoader
|
||||||
|
import org.jetbrains.jewel.styling.ResourcePainterProvider
|
||||||
|
|
||||||
|
class IntelliJResourcePainterProvider<T : InteractiveComponentState>(
|
||||||
|
basePath: String,
|
||||||
|
svgLoader: SvgLoader,
|
||||||
|
prefixTokensProvider: (state: T) -> String = { "" },
|
||||||
|
suffixTokensProvider: (state: T) -> String = { "" },
|
||||||
|
) : ResourcePainterProvider<T>(basePath, svgLoader, prefixTokensProvider, suffixTokensProvider) {
|
||||||
|
|
||||||
|
private val logger = thisLogger()
|
||||||
|
|
||||||
|
private val mappingsByClassLoader
|
||||||
|
get() = service<IconMapLoader>().loadIconMapping()
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun patchPath(state: T, basePath: String, resourceLoader: ResourceLoader): String {
|
||||||
|
val patchedPath = super.patchPath(state, basePath, resourceLoader)
|
||||||
|
return mapPath(patchedPath, resourceLoader)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapPath(originalPath: String, resourceLoader: ResourceLoader): String {
|
||||||
|
logger.debug("Loading SVG from '$originalPath'")
|
||||||
|
val searchClasses = (resourceLoader as? JewelResourceLoader)?.searchClasses
|
||||||
|
if (searchClasses == null) {
|
||||||
|
logger.warn(
|
||||||
|
"Tried loading a resource but the provided ResourceLoader is now a JewelResourceLoader; " +
|
||||||
|
"this is probably a bug. Make sure you always use JewelResourceLoaders.",
|
||||||
|
)
|
||||||
|
return originalPath
|
||||||
|
}
|
||||||
|
|
||||||
|
val allMappings = mappingsByClassLoader
|
||||||
|
if (allMappings.isEmpty()) {
|
||||||
|
logger.info("No mapping info available yet, can't check for '$originalPath' mapping.")
|
||||||
|
return originalPath
|
||||||
|
}
|
||||||
|
|
||||||
|
val applicableMappings = searchClasses.mapNotNull { allMappings[it.classLoader] }
|
||||||
|
val mappedPath = applicableMappings.firstNotNullOfOrNull { it[originalPath.removePrefix("/")] }
|
||||||
|
|
||||||
|
if (mappedPath == null) {
|
||||||
|
logger.debug("Icon '$originalPath' has no mapping defined.")
|
||||||
|
return originalPath
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Icon '$originalPath' is mapped to '$mappedPath'.")
|
||||||
|
return mappedPath
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.jetbrains.jewel.bridge
|
||||||
|
|
||||||
|
sealed class JewelBridgeException(override val message: String?) : RuntimeException(message) {
|
||||||
|
|
||||||
|
class KeyNotFoundException(key: String, type: String) : JewelBridgeException(
|
||||||
|
"Key '$key' not found in Swing LaF, was expecting a value of type $type",
|
||||||
|
)
|
||||||
|
|
||||||
|
class KeysNotFoundException(keys: List<String>, type: String) : JewelBridgeException(
|
||||||
|
"Keys ${keys.joinToString(", ") { "'$it'" }} not found in Swing LaF, " +
|
||||||
|
"was expecting a value of type $type",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE") // Same implementation as error()
|
||||||
|
internal inline fun keyNotFound(key: String, type: String): Nothing =
|
||||||
|
throw JewelBridgeException.KeyNotFoundException(key, type)
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE") // Same implementation as error()
|
||||||
|
internal inline fun keysNotFound(keys: List<String>, type: String): Nothing =
|
||||||
|
throw JewelBridgeException.KeysNotFoundException(keys, type)
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package org.jetbrains.jewel.bridge
|
|
||||||
|
|
||||||
import com.intellij.openapi.Disposable
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
|
|
||||||
internal class ProjectLifecycle : Disposable, CoroutineScope {
|
|
||||||
|
|
||||||
override val coroutineContext = SupervisorJob()
|
|
||||||
|
|
||||||
override fun dispose() = cancel()
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package org.jetbrains.jewel.bridge
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.res.loadSvgPainter
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun rememberSvgResource(resourcePath: String, classLoader: ClassLoader): Painter {
|
|
||||||
val density = LocalDensity.current
|
|
||||||
return remember(resourcePath, density) {
|
|
||||||
useResource(resourcePath, classLoader) {
|
|
||||||
loadSvgPainter(it, density)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> useResource(
|
|
||||||
resourcePath: String,
|
|
||||||
classLoader: ClassLoader,
|
|
||||||
block: (InputStream) -> T
|
|
||||||
): T = openResource(resourcePath, classLoader).use(block)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open [InputStream] from a resource stored in resources for the application.
|
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException if there is no [resourcePath] in resources
|
|
||||||
*/
|
|
||||||
@PublishedApi
|
|
||||||
internal fun openResource(resourcePath: String, classLoader: ClassLoader): InputStream {
|
|
||||||
return requireNotNull(classLoader.getResourceAsStream(resourcePath)) {
|
|
||||||
"Resource $resourcePath not found"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package org.jetbrains.jewel.bridge
|
||||||
|
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import com.intellij.openapi.Disposable
|
||||||
|
import com.intellij.openapi.components.Service
|
||||||
|
import com.intellij.openapi.components.Service.Level
|
||||||
|
import com.intellij.openapi.diagnostic.thisLogger
|
||||||
|
import com.intellij.ui.NewUI
|
||||||
|
import kotlinx.coroutines.CoroutineName
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import org.jetbrains.jewel.IntelliJComponentStyling
|
||||||
|
import org.jetbrains.jewel.IntelliJSvgLoader
|
||||||
|
import org.jetbrains.jewel.SvgLoader
|
||||||
|
import org.jetbrains.jewel.themes.PaletteMapperFactory
|
||||||
|
import org.jetbrains.jewel.themes.intui.core.IntUiThemeDefinition
|
||||||
|
import org.jetbrains.jewel.themes.intui.core.IntelliJSvgPatcher
|
||||||
|
|
||||||
|
@Service(Level.APP)
|
||||||
|
class SwingBridgeService : Disposable {
|
||||||
|
|
||||||
|
private val logger = thisLogger()
|
||||||
|
|
||||||
|
// TODO use constructor injection when min IJ is 232+
|
||||||
|
private val coroutineScope: CoroutineScope =
|
||||||
|
CoroutineScope(SupervisorJob() + CoroutineName("JewelSwingBridge"))
|
||||||
|
|
||||||
|
// TODO we shouldn't assume it's Int UI, but we only have that for now
|
||||||
|
internal val currentBridgeThemeData: StateFlow<BridgeThemeData> =
|
||||||
|
IntelliJApplication.lookAndFeelChangedFlow(coroutineScope)
|
||||||
|
.map { readThemeData() }
|
||||||
|
.stateIn(coroutineScope, SharingStarted.Eagerly, BridgeThemeData.DEFAULT)
|
||||||
|
|
||||||
|
private suspend fun readThemeData(): BridgeThemeData {
|
||||||
|
val isIntUi = NewUI.isEnabled()
|
||||||
|
if (!isIntUi) {
|
||||||
|
// TODO return Darcula/IntelliJ Light theme instead
|
||||||
|
logger.warn("Darcula LaFs (aka \"old UI\") are not supported yet, falling back to Int UI")
|
||||||
|
}
|
||||||
|
|
||||||
|
val themeDefinition = createBridgeIntUiDefinition()
|
||||||
|
val svgLoader = createSvgLoader(themeDefinition)
|
||||||
|
return BridgeThemeData(
|
||||||
|
themeDefinition = createBridgeIntUiDefinition(),
|
||||||
|
svgLoader = svgLoader,
|
||||||
|
componentStyling = createSwingIntUiComponentStyling(themeDefinition, svgLoader),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
coroutineScope.cancel("Disposing Application...")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal data class BridgeThemeData(
|
||||||
|
val themeDefinition: IntUiThemeDefinition,
|
||||||
|
val svgLoader: SvgLoader,
|
||||||
|
val componentStyling: IntelliJComponentStyling,
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val DEFAULT = run {
|
||||||
|
val themeDefinition = createBridgeIntUiDefinition(TextStyle.Default)
|
||||||
|
val svgLoader = createSvgLoader(themeDefinition)
|
||||||
|
BridgeThemeData(
|
||||||
|
themeDefinition = createBridgeIntUiDefinition(TextStyle.Default),
|
||||||
|
svgLoader = createSvgLoader(themeDefinition),
|
||||||
|
componentStyling = createSwingIntUiComponentStyling(
|
||||||
|
theme = themeDefinition,
|
||||||
|
svgLoader = svgLoader,
|
||||||
|
textAreaTextStyle = TextStyle.Default,
|
||||||
|
textFieldTextStyle = TextStyle.Default,
|
||||||
|
dropdownTextStyle = TextStyle.Default,
|
||||||
|
labelTextStyle = TextStyle.Default,
|
||||||
|
linkTextStyle = TextStyle.Default,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSvgLoader(theme: IntUiThemeDefinition): SvgLoader {
|
||||||
|
val paletteMapper = PaletteMapperFactory.create(theme.isDark, theme.iconData, theme.colorPalette)
|
||||||
|
val svgPatcher = IntelliJSvgPatcher(paletteMapper)
|
||||||
|
return IntelliJSvgLoader(svgPatcher)
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.jetbrains.jewel.bridge
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
|
import org.jetbrains.jewel.ExperimentalJewelApi
|
||||||
|
import org.jetbrains.jewel.themes.intui.standalone.IntUiTheme
|
||||||
|
|
||||||
|
private val bridgeService = service<SwingBridgeService>()
|
||||||
|
|
||||||
|
@ExperimentalJewelApi
|
||||||
|
@Composable
|
||||||
|
fun SwingBridgeTheme(content: @Composable () -> Unit) {
|
||||||
|
val themeData by bridgeService.currentBridgeThemeData.collectAsState()
|
||||||
|
|
||||||
|
// TODO handle non-Int UI themes, too
|
||||||
|
IntUiTheme(themeData.themeDefinition, themeData.componentStyling, swingCompatMode = true, content)
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package org.jetbrains.jewel
|
package org.jetbrains.jewel.bridge
|
||||||
|
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
@Target(
|
@Target(
|
||||||
AnnotationTarget.PROPERTY,
|
AnnotationTarget.PROPERTY,
|
||||||
AnnotationTarget.PROPERTY_GETTER,
|
AnnotationTarget.PROPERTY_GETTER,
|
||||||
AnnotationTarget.VALUE_PARAMETER,
|
AnnotationTarget.VALUE_PARAMETER,
|
||||||
AnnotationTarget.EXPRESSION
|
AnnotationTarget.EXPRESSION,
|
||||||
)
|
)
|
||||||
annotation class SwingLafKey(
|
annotation class SwingLafKey(
|
||||||
val key: String,
|
val key: String,
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user