mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-14 18:05:27 +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
|
||||
|
||||
- name: Run :check task
|
||||
run: ./gradlew check
|
||||
run: ./gradlew check --continue
|
||||
|
||||
- name: Merge SARIF reports
|
||||
# Necessary because upload-sarif only takes up to 15 SARIF files and we have more
|
||||
run: ./gradlew :mergeSarifReports
|
||||
if: ${{ always() }}
|
||||
|
||||
- uses: github/codeql-action/upload-sarif@v2
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
sarif_file: ${{ github.workspace }}/build/reports/static-analysis.sarif
|
||||
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="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<editorconfig>
|
||||
<option name="ENABLED" value="false" />
|
||||
</editorconfig>
|
||||
<codeStyleSettings language="JSON">
|
||||
<indentOptions>
|
||||
<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">
|
||||
<profile version="1.0">
|
||||
<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="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="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="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="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="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="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="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="NoScrollPane" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="NonAtomicOperationOnVolatileField" enabled="false" level="WARNING" 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="StructuralWrap" enabled="false" level="TYPO" 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="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>
|
||||
</component>
|
||||
@@ -1,5 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<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>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$/samples/ide-plugin" />
|
||||
@@ -18,6 +19,7 @@
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>false</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,14 +1,18 @@
|
||||
[](https://github.com/JetBrains#jetbrains-on-github) [](https://github.com/JetBrains/jewel/actions/workflows/build.yml)
|
||||
|
||||
# Jewel: a Compose for Desktop theme
|
||||
|
||||
<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**
|
||||
>This project is in very early development and is probably not ready to be used in production projects. You _can_, but there
|
||||
>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!
|
||||
> This project is in very early development and is probably not ready to be used in production projects. You _can_, but
|
||||
> there
|
||||
> 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
|
||||
|
||||
@@ -19,7 +23,8 @@ The project is split in modules:
|
||||
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-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
|
||||
on that later)
|
||||
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 IntelliJ IDEA plugin sample, you can run the `:samples:ide-plugin:runIde` Gradle task. This will download and 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
|
||||
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
|
||||
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).
|
||||
|
||||
If you're using IntelliJ IDEA, you can use the "Stand-alone sample" and "IDE sample" run configurations.
|
||||
|
||||
### 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
[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
|
||||
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
|
||||
[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.
|
||||
|
||||
To find the required values in the IDE, we recommend enabling
|
||||
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
|
||||
[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.
|
||||
|
||||
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 {
|
||||
sarif(projects.foundation)
|
||||
sarif(projects.core)
|
||||
sarif(projects.composeUtils)
|
||||
sarif(projects.samples.standalone)
|
||||
|
||||
@@ -35,6 +35,7 @@ kotlin {
|
||||
optIn("kotlin.experimental.ExperimentalTypeInference")
|
||||
optIn("androidx.compose.ui.ExperimentalComposeUiApi")
|
||||
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 io.gitlab.arturbosch.detekt.Detekt
|
||||
import java.net.URL
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
@@ -23,7 +22,8 @@ import org.gradle.kotlin.dsl.register
|
||||
import org.gradle.kotlin.dsl.withType
|
||||
import org.gradle.util.internal.GUtil
|
||||
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> {
|
||||
|
||||
@@ -42,7 +42,7 @@ abstract class IntelliJThemeGeneratorPlugin : Plugin<Project> {
|
||||
ideaVersion.set(this@all.ideaVersion)
|
||||
themeFile.set(this@all.themeFile)
|
||||
}
|
||||
tasks.withType<KotlinCompile> {
|
||||
tasks.withType<BaseKotlinCompile> {
|
||||
dependsOn(task)
|
||||
}
|
||||
tasks.withType<Detekt> {
|
||||
|
||||
@@ -7,6 +7,5 @@ dependencies {
|
||||
api(compose.desktop.currentOs) {
|
||||
exclude(group = "org.jetbrains.compose.material")
|
||||
}
|
||||
implementation(libs.jna)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,5 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
api(projects.composeUtils)
|
||||
api(projects.foundation)
|
||||
api(compose.desktop.common)
|
||||
}
|
||||
|
||||
@@ -29,11 +29,9 @@ import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Active
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Enabled
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Error
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Focused
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Hovered
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Pressed
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Warning
|
||||
import org.jetbrains.jewel.foundation.Stroke
|
||||
import org.jetbrains.jewel.foundation.border
|
||||
import org.jetbrains.jewel.styling.ButtonStyle
|
||||
@@ -55,7 +53,7 @@ fun DefaultButton(
|
||||
interactionSource = interactionSource,
|
||||
style = style,
|
||||
content = content,
|
||||
textStyle = textStyle
|
||||
textStyle = textStyle,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -76,7 +74,7 @@ fun OutlinedButton(
|
||||
interactionSource = interactionSource,
|
||||
style = style,
|
||||
content = content,
|
||||
textStyle = textStyle
|
||||
textStyle = textStyle,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -117,23 +115,27 @@ private fun ButtonImpl(
|
||||
val shape = RoundedCornerShape(style.metrics.cornerSize)
|
||||
val colors = style.colors
|
||||
val borderColor by colors.borderFor(buttonState)
|
||||
println("state: $buttonState ($enabled) -> $borderColor")
|
||||
|
||||
Box(
|
||||
modifier.clickable(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
role = Role.Button,
|
||||
interactionSource = interactionSource,
|
||||
indication = null
|
||||
).background(colors.backgroundFor(buttonState).value, shape)
|
||||
modifier = modifier
|
||||
.clickable(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
role = Role.Button,
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
)
|
||||
.background(colors.backgroundFor(buttonState).value, shape)
|
||||
.border(Stroke.Alignment.Center, style.metrics.borderWidth, borderColor, shape)
|
||||
.focusOutline(buttonState, shape),
|
||||
propagateMinConstraints = true
|
||||
propagateMinConstraints = true,
|
||||
) {
|
||||
val contentColor by colors.contentFor(buttonState)
|
||||
|
||||
CompositionLocalProvider(
|
||||
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(
|
||||
Modifier
|
||||
@@ -141,7 +143,7 @@ private fun ButtonImpl(
|
||||
.padding(style.metrics.padding),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
content = content
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -182,7 +184,7 @@ value class ButtonState(val state: ULong) : FocusableComponentState {
|
||||
focused = focused,
|
||||
pressed = pressed,
|
||||
hovered = hovered,
|
||||
active = active
|
||||
active = active,
|
||||
)
|
||||
|
||||
override fun toString() =
|
||||
@@ -194,19 +196,15 @@ value class ButtonState(val state: ULong) : FocusableComponentState {
|
||||
fun of(
|
||||
enabled: Boolean = true,
|
||||
focused: Boolean = false,
|
||||
error: Boolean = false,
|
||||
pressed: Boolean = false,
|
||||
hovered: Boolean = false,
|
||||
warning: Boolean = false,
|
||||
active: Boolean = true,
|
||||
) = ButtonState(
|
||||
state = (if (enabled) Enabled else 0UL) or
|
||||
(if (focused) Focused else 0UL) or
|
||||
(if (hovered) Hovered else 0UL) or
|
||||
(if (pressed) Pressed else 0UL) or
|
||||
(if (warning) Warning else 0UL) or
|
||||
(if (error) Error else 0UL) or
|
||||
(if (active) Active else 0UL)
|
||||
(if (active) Active else 0UL),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ fun Checkbox(
|
||||
icons = icons,
|
||||
textStyle = textStyle,
|
||||
resourceLoader = resourceLoader,
|
||||
content = null
|
||||
content = null,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ fun TriStateCheckbox(
|
||||
icons = icons,
|
||||
textStyle = textStyle,
|
||||
resourceLoader = resourceLoader,
|
||||
content = null
|
||||
content = null,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ fun TriStateCheckboxRow(
|
||||
metrics = metrics,
|
||||
icons = icons,
|
||||
resourceLoader = resourceLoader,
|
||||
textStyle = textStyle
|
||||
textStyle = textStyle,
|
||||
) {
|
||||
Text(text)
|
||||
}
|
||||
@@ -165,7 +165,7 @@ fun CheckboxRow(
|
||||
metrics = metrics,
|
||||
icons = icons,
|
||||
resourceLoader = resourceLoader,
|
||||
textStyle = textStyle
|
||||
textStyle = textStyle,
|
||||
) {
|
||||
Text(text)
|
||||
}
|
||||
@@ -200,7 +200,7 @@ fun CheckboxRow(
|
||||
icons = icons,
|
||||
resourceLoader = resourceLoader,
|
||||
textStyle = textStyle,
|
||||
content = content
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ fun TriStateCheckboxRow(
|
||||
icons = icons,
|
||||
resourceLoader = resourceLoader,
|
||||
textStyle = textStyle,
|
||||
content = content
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -280,16 +280,16 @@ private fun CheckboxImpl(
|
||||
enabled = enabled,
|
||||
role = Role.Checkbox,
|
||||
interactionSource = interactionSource,
|
||||
indication = null
|
||||
indication = null,
|
||||
)
|
||||
|
||||
val checkBoxImageModifier = Modifier.size(metrics.checkboxSize)
|
||||
.outline(
|
||||
state = checkboxState,
|
||||
outline = outline,
|
||||
alignment = Stroke.Alignment.Center,
|
||||
outlineShape = RoundedCornerShape(metrics.checkboxCornerSize),
|
||||
outlineWidth = metrics.outlineWidth
|
||||
alignment = Stroke.Alignment.Center,
|
||||
outlineWidth = metrics.outlineWidth,
|
||||
)
|
||||
|
||||
val checkboxPainter by icons.checkbox.getPainter(checkboxState, resourceLoader)
|
||||
@@ -300,14 +300,14 @@ private fun CheckboxImpl(
|
||||
Row(
|
||||
wrapperModifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(metrics.iconContentGap),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CheckBoxImage(Modifier, checkboxPainter, checkBoxImageModifier)
|
||||
|
||||
val contentColor by colors.contentFor(checkboxState)
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides textStyle.copy(color = contentColor.takeOrElse { textStyle.color }),
|
||||
LocalContentColor provides contentColor.takeOrElse { textStyle.color }
|
||||
LocalContentColor provides contentColor.takeOrElse { textStyle.color },
|
||||
) {
|
||||
content()
|
||||
}
|
||||
@@ -367,7 +367,7 @@ value class CheckboxState(private val state: ULong) : ToggleableComponentState {
|
||||
focused = focused,
|
||||
pressed = pressed,
|
||||
hovered = hovered,
|
||||
active = active
|
||||
active = active,
|
||||
)
|
||||
|
||||
override fun toString() =
|
||||
@@ -390,7 +390,7 @@ value class CheckboxState(private val state: ULong) : ToggleableComponentState {
|
||||
(if (pressed) Pressed else 0UL) or
|
||||
(if (toggleableState != ToggleableState.Off) Selected 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.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
@@ -26,11 +27,10 @@ import androidx.compose.ui.graphics.isUnspecified
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Active
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Enabled
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Error
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Focused
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Hovered
|
||||
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.border
|
||||
import org.jetbrains.jewel.styling.ChipStyle
|
||||
@@ -40,15 +40,95 @@ fun Chip(
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
enabled: Boolean = true,
|
||||
selected: Boolean = false,
|
||||
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,
|
||||
) {
|
||||
var chipState by remember(interactionSource) {
|
||||
mutableStateOf(ChipState.of(enabled = enabled))
|
||||
mutableStateOf(ChipState.of(enabled = enabled, selected = selected))
|
||||
}
|
||||
remember(enabled) {
|
||||
chipState = chipState.copy(enabled = enabled)
|
||||
remember(enabled, selected) {
|
||||
chipState = chipState.copy(enabled = enabled, selected = selected)
|
||||
}
|
||||
|
||||
LaunchedEffect(interactionSource) {
|
||||
@@ -70,20 +150,12 @@ fun Chip(
|
||||
|
||||
Row(
|
||||
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)
|
||||
.border(Stroke.Alignment.Center, style.metrics.borderWidth, borderColor, shape)
|
||||
.outline(chipState, shape),
|
||||
.focusOutline(chipState, shape)
|
||||
.padding(style.metrics.padding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides (
|
||||
@@ -92,7 +164,7 @@ fun Chip(
|
||||
.value
|
||||
.takeIf { !it.isUnspecified }
|
||||
?: LocalContentColor.current
|
||||
)
|
||||
),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
@@ -101,7 +173,7 @@ fun Chip(
|
||||
|
||||
@Immutable
|
||||
@JvmInline
|
||||
value class ChipState(val state: ULong) : StateWithOutline {
|
||||
value class ChipState(val state: ULong) : FocusableComponentState, SelectableComponentState {
|
||||
|
||||
@Stable
|
||||
override val isActive: Boolean
|
||||
@@ -116,12 +188,8 @@ value class ChipState(val state: ULong) : StateWithOutline {
|
||||
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
|
||||
override val isSelected: Boolean
|
||||
get() = state and Selected != 0UL
|
||||
|
||||
@Stable
|
||||
override val isHovered: Boolean
|
||||
@@ -134,23 +202,21 @@ value class ChipState(val state: ULong) : StateWithOutline {
|
||||
fun copy(
|
||||
enabled: Boolean = isEnabled,
|
||||
focused: Boolean = isFocused,
|
||||
error: Boolean = isError,
|
||||
selected: Boolean = isSelected,
|
||||
pressed: Boolean = isPressed,
|
||||
hovered: Boolean = isHovered,
|
||||
warning: Boolean = isWarning,
|
||||
active: Boolean = isActive,
|
||||
): ChipState = of(
|
||||
enabled = enabled,
|
||||
focused = focused,
|
||||
error = error,
|
||||
pressed = pressed,
|
||||
hovered = hovered,
|
||||
warning = warning,
|
||||
active = active
|
||||
active = active,
|
||||
selected = selected,
|
||||
)
|
||||
|
||||
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)"
|
||||
|
||||
companion object {
|
||||
@@ -158,19 +224,17 @@ value class ChipState(val state: ULong) : StateWithOutline {
|
||||
fun of(
|
||||
enabled: Boolean = true,
|
||||
focused: Boolean = false,
|
||||
error: Boolean = false,
|
||||
selected: Boolean = false,
|
||||
pressed: Boolean = false,
|
||||
hovered: Boolean = false,
|
||||
warning: Boolean = false,
|
||||
active: Boolean = false,
|
||||
) = ChipState(
|
||||
state = (if (enabled) Enabled 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 (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.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalInputModeManager
|
||||
import androidx.compose.ui.res.ResourceLoader
|
||||
import androidx.compose.ui.window.Popup
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
import androidx.compose.ui.window.rememberCursorPositionProvider
|
||||
import org.jetbrains.jewel.styling.MenuStyle
|
||||
|
||||
@@ -31,7 +33,8 @@ object IntelliJContextMenuRepresentation : ContextMenuRepresentation {
|
||||
state.status = ContextMenuState.Status.Closed
|
||||
true
|
||||
},
|
||||
style = IntelliJTheme.menuStyle
|
||||
style = IntelliJTheme.menuStyle,
|
||||
resourceLoader = LocalResourceLoader.current,
|
||||
) {
|
||||
contextItems(items)
|
||||
}
|
||||
@@ -42,8 +45,9 @@ object IntelliJContextMenuRepresentation : ContextMenuRepresentation {
|
||||
@Composable
|
||||
internal fun ContextMenu(
|
||||
onDismissRequest: (InputMode) -> Boolean,
|
||||
focusable: Boolean = true,
|
||||
resourceLoader: ResourceLoader,
|
||||
modifier: Modifier = Modifier,
|
||||
focusable: Boolean = true,
|
||||
style: MenuStyle = IntelliJTheme.menuStyle,
|
||||
content: MenuScope.() -> Unit,
|
||||
) {
|
||||
@@ -51,31 +55,33 @@ internal fun ContextMenu(
|
||||
var inputModeManager: InputModeManager? by mutableStateOf(null)
|
||||
val menuManager = remember(onDismissRequest) {
|
||||
MenuManager(
|
||||
onDismissRequest = onDismissRequest
|
||||
onDismissRequest = onDismissRequest,
|
||||
)
|
||||
}
|
||||
|
||||
Popup(
|
||||
focusable = focusable,
|
||||
popupPositionProvider = rememberCursorPositionProvider(style.metrics.offset),
|
||||
onDismissRequest = {
|
||||
onDismissRequest(InputMode.Touch)
|
||||
},
|
||||
popupPositionProvider = rememberCursorPositionProvider(style.metrics.offset),
|
||||
properties = PopupProperties(focusable = focusable),
|
||||
onPreviewKeyEvent = { false },
|
||||
onKeyEvent = {
|
||||
val currentFocusManager = checkNotNull(focusManager) { "FocusManager must not be null" }
|
||||
val currentInputModeManager = checkNotNull(inputModeManager) { "InputModeManager must not be null" }
|
||||
handlePopupMenuOnKeyEvent(it, currentFocusManager, currentInputModeManager, menuManager)
|
||||
}
|
||||
},
|
||||
) {
|
||||
focusManager = LocalFocusManager.current
|
||||
inputModeManager = LocalInputModeManager.current
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalMenuManager provides menuManager
|
||||
LocalMenuManager provides menuManager,
|
||||
) {
|
||||
MenuContent(
|
||||
modifier = modifier,
|
||||
content = content
|
||||
content = content,
|
||||
resourceLoader = resourceLoader,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -85,7 +91,7 @@ private fun MenuScope.contextItems(items: () -> List<ContextMenuItem>) {
|
||||
items().forEach { item ->
|
||||
when (item) {
|
||||
is ContextMenuDivider -> {
|
||||
divider()
|
||||
separator()
|
||||
}
|
||||
|
||||
is ContextSubmenu -> {
|
||||
@@ -99,7 +105,7 @@ private fun MenuScope.contextItems(items: () -> List<ContextMenuItem>) {
|
||||
else -> {
|
||||
selectableItem(
|
||||
selected = false,
|
||||
onClick = item.onClick
|
||||
onClick = item.onClick,
|
||||
) {
|
||||
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
|
||||
.then(indentMod)
|
||||
.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.semantics.Role
|
||||
import androidx.compose.ui.window.Popup
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Active
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Enabled
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Error
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Focused
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Hovered
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Pressed
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Warning
|
||||
import org.jetbrains.jewel.foundation.Stroke
|
||||
import org.jetbrains.jewel.foundation.border
|
||||
import org.jetbrains.jewel.styling.DropdownStyle
|
||||
@@ -64,11 +63,11 @@ fun Dropdown(
|
||||
var skipNextClick by remember { mutableStateOf(false) }
|
||||
|
||||
var dropdownState by remember(interactionSource) {
|
||||
mutableStateOf(DropdownState.of(enabled = enabled, outline = outline))
|
||||
mutableStateOf(DropdownState.of(enabled = enabled))
|
||||
}
|
||||
|
||||
remember(enabled, outline) {
|
||||
dropdownState = dropdownState.copy(enabled = enabled, outline = Outline.Error)
|
||||
remember(enabled) {
|
||||
dropdownState = dropdownState.copy(enabled = enabled)
|
||||
}
|
||||
|
||||
LaunchedEffect(interactionSource) {
|
||||
@@ -91,6 +90,7 @@ fun Dropdown(
|
||||
val metrics = style.metrics
|
||||
val shape = RoundedCornerShape(style.metrics.cornerSize)
|
||||
val minSize = metrics.minSize
|
||||
val arrowMinSize = style.metrics.arrowMinSize
|
||||
val borderColor by colors.borderFor(dropdownState)
|
||||
|
||||
Box(
|
||||
@@ -105,33 +105,37 @@ fun Dropdown(
|
||||
enabled = enabled,
|
||||
role = Role.Button,
|
||||
interactionSource = interactionSource,
|
||||
indication = null
|
||||
).background(colors.backgroundFor(dropdownState).value, shape)
|
||||
indication = null,
|
||||
)
|
||||
.background(colors.backgroundFor(dropdownState).value, shape)
|
||||
.border(Stroke.Alignment.Center, style.metrics.borderWidth, borderColor, shape)
|
||||
.defaultMinSize(minSize.width, minSize.height),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
.outline(dropdownState, outline, shape)
|
||||
.defaultMinSize(minSize.width, minSize.height.coerceAtLeast(arrowMinSize.height)),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides colors.contentFor(dropdownState).value
|
||||
LocalContentColor provides colors.contentFor(dropdownState).value,
|
||||
) {
|
||||
Row(
|
||||
Modifier.padding(style.metrics.contentPadding).padding(end = minSize.height),
|
||||
modifier = Modifier.padding(style.metrics.contentPadding)
|
||||
.padding(end = minSize.height),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
content = {
|
||||
content()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier.size(minSize.height).align(Alignment.CenterEnd),
|
||||
contentAlignment = Alignment.Center
|
||||
modifier = Modifier.size(arrowMinSize)
|
||||
.align(Alignment.CenterEnd),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val chevronIcon by style.icons.chevronDown.getPainter(dropdownState, resourceLoader)
|
||||
Icon(
|
||||
painter = chevronIcon,
|
||||
contentDescription = null,
|
||||
tint = colors.iconTintFor(dropdownState).value
|
||||
tint = colors.iconTintFor(dropdownState).value,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -149,7 +153,8 @@ fun Dropdown(
|
||||
modifier = menuModifier,
|
||||
style = style.menuStyle,
|
||||
horizontalAlignment = Alignment.Start,
|
||||
content = menuContent
|
||||
content = menuContent,
|
||||
resourceLoader = resourceLoader,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -159,6 +164,7 @@ fun Dropdown(
|
||||
internal fun DropdownMenu(
|
||||
onDismissRequest: (InputMode) -> Boolean,
|
||||
horizontalAlignment: Alignment.Horizontal,
|
||||
resourceLoader: ResourceLoader,
|
||||
modifier: Modifier = Modifier,
|
||||
style: MenuStyle,
|
||||
content: MenuScope.() -> Unit,
|
||||
@@ -167,9 +173,9 @@ internal fun DropdownMenu(
|
||||
|
||||
val popupPositionProvider = AnchorVerticalMenuPositionProvider(
|
||||
contentOffset = style.metrics.offset,
|
||||
contentMargin = style.metrics.margin,
|
||||
contentMargin = style.metrics.menuMargin,
|
||||
alignment = horizontalAlignment,
|
||||
density = density
|
||||
density = density,
|
||||
)
|
||||
|
||||
var focusManager: FocusManager? by mutableStateOf(null)
|
||||
@@ -179,25 +185,27 @@ internal fun DropdownMenu(
|
||||
}
|
||||
|
||||
Popup(
|
||||
onDismissRequest = { onDismissRequest(InputMode.Touch) },
|
||||
popupPositionProvider = popupPositionProvider,
|
||||
onDismissRequest = { onDismissRequest(InputMode.Touch) },
|
||||
properties = PopupProperties(focusable = true),
|
||||
onPreviewKeyEvent = { false },
|
||||
onKeyEvent = {
|
||||
val currentFocusManager = checkNotNull(focusManager) { "FocusManager must not be null" }
|
||||
val currentInputModeManager = checkNotNull(inputModeManager) { "InputModeManager must not be null" }
|
||||
handlePopupMenuOnKeyEvent(it, currentFocusManager, currentInputModeManager, menuManager)
|
||||
},
|
||||
focusable = true
|
||||
) {
|
||||
focusManager = LocalFocusManager.current
|
||||
inputModeManager = LocalInputModeManager.current
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalMenuManager provides menuManager,
|
||||
LocalMenuStyle provides style
|
||||
LocalMenuStyle provides style,
|
||||
) {
|
||||
MenuContent(
|
||||
modifier = modifier,
|
||||
content = content
|
||||
content = content,
|
||||
resourceLoader = resourceLoader,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -205,7 +213,7 @@ internal fun DropdownMenu(
|
||||
|
||||
@Immutable
|
||||
@JvmInline
|
||||
value class DropdownState(val state: ULong) : StateWithOutline {
|
||||
value class DropdownState(val state: ULong) : FocusableComponentState {
|
||||
|
||||
@Stable
|
||||
override val isActive: Boolean
|
||||
@@ -219,14 +227,6 @@ value class DropdownState(val state: ULong) : StateWithOutline {
|
||||
override val isFocused: Boolean
|
||||
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
|
||||
override val isHovered: Boolean
|
||||
get() = state and Hovered != 0UL
|
||||
@@ -240,19 +240,17 @@ value class DropdownState(val state: ULong) : StateWithOutline {
|
||||
focused: Boolean = isFocused,
|
||||
pressed: Boolean = isPressed,
|
||||
hovered: Boolean = isHovered,
|
||||
outline: Outline = Outline.of(isWarning, isError),
|
||||
active: Boolean = isActive,
|
||||
) = of(
|
||||
enabled = enabled,
|
||||
focused = focused,
|
||||
pressed = pressed,
|
||||
hovered = hovered,
|
||||
outline = outline,
|
||||
active = active
|
||||
active = active,
|
||||
)
|
||||
|
||||
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)"
|
||||
|
||||
companion object {
|
||||
@@ -262,7 +260,6 @@ value class DropdownState(val state: ULong) : StateWithOutline {
|
||||
focused: Boolean = false,
|
||||
pressed: Boolean = false,
|
||||
hovered: Boolean = false,
|
||||
outline: Outline = Outline.None,
|
||||
active: Boolean = false,
|
||||
) = DropdownState(
|
||||
if (enabled) {
|
||||
@@ -272,10 +269,8 @@ value class DropdownState(val state: ULong) : StateWithOutline {
|
||||
(if (focused) Focused else 0UL) or
|
||||
(if (pressed) Pressed 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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package org.jetbrains.jewel
|
||||
@RequiresOptIn(
|
||||
level = RequiresOptIn.Level.WARNING,
|
||||
message = "This is an experimental API for Jewel and is likely to change before becoming " +
|
||||
"stable."
|
||||
"stable.",
|
||||
)
|
||||
@Target(
|
||||
AnnotationTarget.CLASS,
|
||||
AnnotationTarget.FUNCTION
|
||||
AnnotationTarget.FUNCTION,
|
||||
)
|
||||
annotation class ExperimentalJewelApi
|
||||
@@ -8,41 +8,33 @@ import androidx.compose.ui.graphics.Color
|
||||
interface GlobalColors {
|
||||
|
||||
val borders: BorderColors
|
||||
|
||||
val outlines: OutlineColors
|
||||
|
||||
@SwingLafKey("*.infoForeground")
|
||||
val infoContent: Color
|
||||
}
|
||||
|
||||
@Immutable
|
||||
interface BorderColors {
|
||||
|
||||
@SwingLafKey("Component.borderColor")
|
||||
val normal: Color
|
||||
|
||||
@SwingLafKey("Component.focusedBorderColor")
|
||||
val focused: Color
|
||||
|
||||
@SwingLafKey("*.disabledBorderColor")
|
||||
val disabled: Color
|
||||
}
|
||||
|
||||
@Immutable
|
||||
interface OutlineColors {
|
||||
|
||||
@SwingLafKey("*.focusColor")
|
||||
val focused: Color
|
||||
|
||||
@SwingLafKey("Component.warningFocusColor")
|
||||
val focusedWarning: Color
|
||||
|
||||
@SwingLafKey("Component.errorFocusColor")
|
||||
val focusedError: Color
|
||||
|
||||
@SwingLafKey("Component.inactiveWarningFocusColor")
|
||||
val warning: Color
|
||||
|
||||
@SwingLafKey("Component.inactiveErrorFocusColor")
|
||||
val error: Color
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.jetbrains.jewel
|
||||
|
||||
import androidx.compose.foundation.shape.CornerSize
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.unit.Dp
|
||||
@@ -9,7 +8,6 @@ import androidx.compose.ui.unit.Dp
|
||||
interface GlobalMetrics {
|
||||
|
||||
val outlineWidth: Dp
|
||||
val outlineCornerSize: CornerSize
|
||||
}
|
||||
|
||||
val LocalGlobalMetrics = staticCompositionLocalOf<GlobalMetrics> {
|
||||
|
||||
@@ -15,7 +15,7 @@ fun GroupHeader(
|
||||
style: GroupHeaderStyle = LocalGroupHeaderStyle.current,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides style.colors.content
|
||||
LocalContentColor provides style.colors.content,
|
||||
) {
|
||||
Row(modifier, verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(text)
|
||||
@@ -23,7 +23,7 @@ fun GroupHeader(
|
||||
color = style.colors.divider,
|
||||
orientation = Orientation.Horizontal,
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.paint
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
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.Painter
|
||||
import androidx.compose.ui.graphics.toolingGraphicsLayer
|
||||
@@ -32,7 +32,6 @@ import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.xml.sax.InputSource
|
||||
import java.io.InputStream
|
||||
import java.util.jar.JarFile
|
||||
|
||||
/**
|
||||
* Icon component that draws [imageVector] using [tint], defaulting to
|
||||
@@ -58,7 +57,7 @@ fun Icon(
|
||||
painter = rememberVectorPainter(imageVector),
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier,
|
||||
tint = tint
|
||||
tint = tint,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -87,7 +86,7 @@ fun Icon(
|
||||
painter = painter,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier,
|
||||
tint = tint
|
||||
tint = tint,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -95,15 +94,15 @@ fun Icon(
|
||||
fun Icon(
|
||||
resource: String,
|
||||
contentDescription: String?,
|
||||
resourceLoader: ResourceLoader = LocalResourceLoader.current,
|
||||
modifier: Modifier = Modifier,
|
||||
resourceLoader: ResourceLoader = LocalResourceLoader.current,
|
||||
tint: Color = Color.Unspecified,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(resource, resourceLoader),
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier,
|
||||
tint = tint
|
||||
tint = tint,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -123,11 +122,32 @@ fun Icon(
|
||||
@Composable
|
||||
fun Icon(
|
||||
painter: Painter,
|
||||
contentDescription: String? = null,
|
||||
contentDescription: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
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) {
|
||||
Modifier.semantics {
|
||||
this.contentDescription = contentDescription
|
||||
@@ -142,9 +162,9 @@ fun Icon(
|
||||
.paint(
|
||||
painter,
|
||||
colorFilter = colorFilter,
|
||||
contentScale = ContentScale.Fit
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
.then(semantics)
|
||||
.then(semantics),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -202,47 +222,16 @@ private inline fun <T> useResource(
|
||||
block: (InputStream) -> T,
|
||||
): T = loader.load(resourcePath).use(block)
|
||||
|
||||
val LocalResourceLoader = staticCompositionLocalOf<ResourceLoader> {
|
||||
ResourceLoader.Default
|
||||
}
|
||||
|
||||
private fun Modifier.defaultSizeFor(painter: Painter) =
|
||||
this.then(
|
||||
then(
|
||||
if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isInfinite()) {
|
||||
DefaultIconSizeModifier
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
private fun Size.isInfinite() = width.isInfinite() && height.isInfinite()
|
||||
|
||||
// Default icon size, for icons with no intrinsic size information
|
||||
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.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.isSpecified
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
@@ -33,7 +34,7 @@ internal fun InputField(
|
||||
modifier: Modifier,
|
||||
enabled: Boolean,
|
||||
readOnly: Boolean,
|
||||
isError: Boolean,
|
||||
outline: Outline,
|
||||
undecorated: Boolean,
|
||||
visualTransformation: VisualTransformation,
|
||||
keyboardOptions: KeyboardOptions,
|
||||
@@ -47,16 +48,15 @@ internal fun InputField(
|
||||
decorationBox: @Composable (innerTextField: @Composable () -> Unit, state: InputFieldState) -> Unit,
|
||||
) {
|
||||
var inputState by remember(interactionSource) {
|
||||
mutableStateOf(InputFieldState.of(enabled = enabled, error = isError))
|
||||
mutableStateOf(InputFieldState.of(enabled = enabled))
|
||||
}
|
||||
remember(isError, enabled) {
|
||||
inputState = inputState.copy(error = isError, enabled = enabled)
|
||||
remember(enabled) {
|
||||
inputState = inputState.copy(enabled = enabled)
|
||||
}
|
||||
LaunchedEffect(interactionSource) {
|
||||
interactionSource.interactions.collect { interaction ->
|
||||
when (interaction) {
|
||||
is FocusInteraction.Focus -> inputState = inputState.copy(focused = true)
|
||||
|
||||
is FocusInteraction.Unfocus -> inputState = inputState.copy(focused = false)
|
||||
}
|
||||
}
|
||||
@@ -73,26 +73,28 @@ internal fun InputField(
|
||||
val borderColor by style.colors.borderFor(inputState)
|
||||
val borderModifier = Modifier.appendIf(!undecorated && borderColor.isSpecified) {
|
||||
Modifier.border(
|
||||
alignment = Stroke.Alignment.Outside,
|
||||
alignment = Stroke.Alignment.Center,
|
||||
width = style.metrics.borderWidth,
|
||||
color = borderColor,
|
||||
shape = shape
|
||||
shape = shape,
|
||||
)
|
||||
}
|
||||
|
||||
val contentColor by colors.contentFor(inputState)
|
||||
val mergedTextStyle = style.textStyle.merge(textStyle).copy(color = contentColor)
|
||||
val cursorBrush by colors.cursorFor(inputState)
|
||||
val caretColor by colors.caretFor(inputState)
|
||||
|
||||
BasicTextField(
|
||||
value = value,
|
||||
modifier = modifier.then(backgroundModifier)
|
||||
.then(borderModifier),
|
||||
.then(borderModifier)
|
||||
.focusOutline(inputState, shape)
|
||||
.outline(inputState, outline, shape),
|
||||
onValueChange = onValueChange,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
textStyle = mergedTextStyle,
|
||||
cursorBrush = cursorBrush,
|
||||
cursorBrush = SolidColor(caretColor),
|
||||
visualTransformation = visualTransformation,
|
||||
onTextLayout = onTextLayout,
|
||||
keyboardOptions = keyboardOptions,
|
||||
@@ -102,13 +104,13 @@ internal fun InputField(
|
||||
maxLines = maxLines,
|
||||
decorationBox = @Composable { innerTextField: @Composable () -> Unit ->
|
||||
decorationBox(innerTextField, inputState)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@JvmInline
|
||||
value class InputFieldState(val state: ULong) : StateWithOutline {
|
||||
value class InputFieldState(val state: ULong) : FocusableComponentState {
|
||||
|
||||
@Stable
|
||||
override val isActive: Boolean
|
||||
@@ -122,14 +124,6 @@ value class InputFieldState(val state: ULong) : StateWithOutline {
|
||||
override val isFocused: Boolean
|
||||
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
|
||||
override val isHovered: Boolean
|
||||
get() = state and CommonStateBitMask.Hovered != 0UL
|
||||
@@ -141,23 +135,19 @@ value class InputFieldState(val state: ULong) : StateWithOutline {
|
||||
fun copy(
|
||||
enabled: Boolean = isEnabled,
|
||||
focused: Boolean = isFocused,
|
||||
error: Boolean = isError,
|
||||
pressed: Boolean = isPressed,
|
||||
hovered: Boolean = isHovered,
|
||||
warning: Boolean = isWarning,
|
||||
active: Boolean = isActive,
|
||||
) = of(
|
||||
enabled = enabled,
|
||||
focused = focused,
|
||||
error = error,
|
||||
pressed = pressed,
|
||||
hovered = hovered,
|
||||
warning = warning,
|
||||
active = active
|
||||
active = active,
|
||||
)
|
||||
|
||||
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)"
|
||||
|
||||
companion object {
|
||||
@@ -165,19 +155,15 @@ value class InputFieldState(val state: ULong) : StateWithOutline {
|
||||
fun of(
|
||||
enabled: Boolean = true,
|
||||
focused: Boolean = false,
|
||||
error: Boolean = false,
|
||||
pressed: Boolean = false,
|
||||
hovered: Boolean = false,
|
||||
warning: Boolean = false,
|
||||
active: Boolean = false,
|
||||
) = InputFieldState(
|
||||
state = (if (enabled) CommonStateBitMask.Enabled 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 (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
|
||||
class IntelliJComponentStyling(
|
||||
val defaultButtonStyle: ButtonStyle,
|
||||
val outlinedButtonStyle: ButtonStyle,
|
||||
val checkboxStyle: CheckboxStyle,
|
||||
val chipStyle: ChipStyle,
|
||||
val defaultButtonStyle: ButtonStyle,
|
||||
val defaultTabStyle: TabStyle,
|
||||
val dropdownStyle: DropdownStyle,
|
||||
val editorTabStyle: TabStyle,
|
||||
val groupHeaderStyle: GroupHeaderStyle,
|
||||
val horizontalProgressBarStyle: HorizontalProgressBarStyle,
|
||||
val labelledTextFieldStyle: LabelledTextFieldStyle,
|
||||
val lazyTreeStyle: LazyTreeStyle,
|
||||
val linkStyle: LinkStyle,
|
||||
val menuStyle: MenuStyle,
|
||||
val horizontalProgressBarStyle: HorizontalProgressBarStyle,
|
||||
val outlinedButtonStyle: ButtonStyle,
|
||||
val radioButtonStyle: RadioButtonStyle,
|
||||
val scrollbarStyle: ScrollbarStyle,
|
||||
val textAreaStyle: TextAreaStyle,
|
||||
val textFieldStyle: TextFieldStyle,
|
||||
val lazyTreeStyle: LazyTreeStyle,
|
||||
val defaultTabStyle: TabStyle,
|
||||
val editorTabStyle: TabStyle,
|
||||
) {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -85,4 +85,13 @@ class IntelliJComponentStyling(
|
||||
result = 31 * result + editorTabStyle.hashCode()
|
||||
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.Immutable
|
||||
@@ -7,8 +7,6 @@ import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.ResourceLoader
|
||||
import androidx.compose.ui.res.loadSvgPainter
|
||||
import org.jetbrains.jewel.SvgLoader
|
||||
import org.jetbrains.jewel.SvgPatcher
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
@@ -39,22 +37,23 @@ class IntelliJSvgLoader(private val svgPatcher: SvgPatcher) : SvgLoader {
|
||||
): Painter {
|
||||
val density = LocalDensity.current
|
||||
|
||||
val painter = try {
|
||||
useResource(resourcePath, loader) {
|
||||
loadSvgPainter(it.patchColors(), density)
|
||||
val painter =
|
||||
try {
|
||||
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 }
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import org.jetbrains.jewel.styling.ButtonStyle
|
||||
import org.jetbrains.jewel.styling.CheckboxStyle
|
||||
@@ -61,6 +62,11 @@ interface IntelliJTheme {
|
||||
@ReadOnlyComposable
|
||||
get() = LocalTextStyle.current
|
||||
|
||||
val contentColor: Color
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalContentColor.current
|
||||
|
||||
val isDark: Boolean
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
@@ -172,7 +178,6 @@ interface IntelliJTheme {
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalJewelApi
|
||||
@Composable
|
||||
fun IntelliJTheme(
|
||||
theme: IntelliJThemeDefinition,
|
||||
@@ -186,13 +191,15 @@ fun IntelliJTheme(
|
||||
|
||||
@Composable
|
||||
fun IntelliJTheme(theme: IntelliJThemeDefinition, content: @Composable () -> Unit) {
|
||||
val defaultTextStyle = theme.defaultTextStyle
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides theme.defaultTextStyle.color,
|
||||
LocalIsDarkTheme provides theme.isDark,
|
||||
LocalTextStyle provides theme.defaultTextStyle,
|
||||
LocalContentColor provides defaultTextStyle.color,
|
||||
LocalTextStyle provides defaultTextStyle,
|
||||
LocalGlobalColors provides theme.globalColors,
|
||||
LocalGlobalMetrics provides theme.metrics,
|
||||
content = content
|
||||
LocalGlobalMetrics provides theme.globalMetrics,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package org.jetbrains.jewel
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@Stable
|
||||
interface IntelliJThemeColorPalette {
|
||||
|
||||
fun lookup(colorKey: String): Color?
|
||||
}
|
||||
|
||||
@Immutable
|
||||
object EmptyThemeColorPalette : IntelliJThemeColorPalette {
|
||||
|
||||
override fun lookup(colorKey: String): Color? = null
|
||||
|
||||
@@ -8,7 +8,7 @@ interface IntelliJThemeDefinition {
|
||||
|
||||
val isDark: Boolean
|
||||
val globalColors: GlobalColors
|
||||
val metrics: GlobalMetrics
|
||||
val globalMetrics: GlobalMetrics
|
||||
val defaultTextStyle: TextStyle
|
||||
|
||||
val colorPalette: IntelliJThemeColorPalette
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package org.jetbrains.jewel
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
interface IntelliJThemeIconData {
|
||||
|
||||
val iconOverrides: Map<String, String>
|
||||
val colorPalette: Map<String, String>
|
||||
}
|
||||
|
||||
@Immutable
|
||||
object EmptyThemeIconData : IntelliJThemeIconData {
|
||||
|
||||
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 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
|
||||
fun LabelledTextField(
|
||||
label: @Composable () -> Unit,
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
label: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
textFieldModifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
isError: Boolean = false,
|
||||
outline: Outline = Outline.None,
|
||||
hint: @Composable (() -> Unit)? = null,
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
@@ -61,7 +68,7 @@ fun LabelledTextField(
|
||||
textFieldModifier = textFieldModifier,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
isError = isError,
|
||||
outline = outline,
|
||||
hint = hint,
|
||||
placeholder = placeholder,
|
||||
trailingIcon = trailingIcon,
|
||||
@@ -72,10 +79,17 @@ fun LabelledTextField(
|
||||
onTextLayout = onTextLayout,
|
||||
style = style,
|
||||
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
|
||||
fun LabelledTextField(
|
||||
label: @Composable () -> Unit,
|
||||
@@ -85,7 +99,7 @@ fun LabelledTextField(
|
||||
textFieldModifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
isError: Boolean = false,
|
||||
outline: Outline = Outline.None,
|
||||
hint: @Composable (() -> Unit)? = null,
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
@@ -104,7 +118,7 @@ fun LabelledTextField(
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides style.textStyles.label,
|
||||
LocalContentColor provides style.colors.label,
|
||||
content = label
|
||||
content = label,
|
||||
)
|
||||
},
|
||||
textField = {
|
||||
@@ -114,7 +128,7 @@ fun LabelledTextField(
|
||||
modifier = textFieldModifier,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
isError = isError,
|
||||
outline = outline,
|
||||
placeholder = placeholder,
|
||||
trailingIcon = trailingIcon,
|
||||
undecorated = undecorated,
|
||||
@@ -124,7 +138,7 @@ fun LabelledTextField(
|
||||
onTextLayout = onTextLayout,
|
||||
style = style,
|
||||
textStyle = textStyle,
|
||||
interactionSource = interactionSource
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
},
|
||||
hint = hint?.let {
|
||||
@@ -132,11 +146,11 @@ fun LabelledTextField(
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides style.textStyles.hint,
|
||||
LocalContentColor provides style.colors.hint,
|
||||
content = it
|
||||
content = it,
|
||||
)
|
||||
}
|
||||
},
|
||||
style = style
|
||||
style = style,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -164,7 +178,7 @@ private fun LabelledTextFieldLayout(
|
||||
hint()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) { measurables, incomingConstraints ->
|
||||
val hintMeasurable = measurables.firstOrNull { it.layoutId == HINT_ID }
|
||||
|
||||
@@ -173,7 +187,7 @@ private fun LabelledTextFieldLayout(
|
||||
|
||||
val constraintsWithoutSpacing = incomingConstraints.offset(
|
||||
horizontal = -horizontalSpacing,
|
||||
vertical = -verticalSpacing
|
||||
vertical = -verticalSpacing,
|
||||
)
|
||||
|
||||
val textFieldPlaceable = measurables.first { it.layoutId == TEXT_FIELD_ID }
|
||||
@@ -185,7 +199,7 @@ private fun LabelledTextFieldLayout(
|
||||
val hintPlaceable = hintMeasurable?.measure(
|
||||
constraintsWithoutSpacing
|
||||
.offset(vertical = -textFieldPlaceable.height)
|
||||
.copy(maxWidth = textFieldPlaceable.width)
|
||||
.copy(maxWidth = textFieldPlaceable.width),
|
||||
)
|
||||
|
||||
val width = labelPlaceable.width + textFieldPlaceable.width + horizontalSpacing
|
||||
@@ -194,15 +208,15 @@ private fun LabelledTextFieldLayout(
|
||||
layout(width, height) {
|
||||
labelPlaceable.placeRelative(
|
||||
x = 0,
|
||||
y = Alignment.CenterVertically.align(labelPlaceable.height, textFieldPlaceable.height)
|
||||
y = Alignment.CenterVertically.align(labelPlaceable.height, textFieldPlaceable.height),
|
||||
)
|
||||
textFieldPlaceable.placeRelative(
|
||||
x = labelPlaceable.width + horizontalSpacing,
|
||||
y = 0
|
||||
y = 0,
|
||||
)
|
||||
hintPlaceable?.placeRelative(
|
||||
x = labelPlaceable.width + horizontalSpacing,
|
||||
y = textFieldPlaceable.height + verticalSpacing
|
||||
y = textFieldPlaceable.height + verticalSpacing,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package org.jetbrains.jewel
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.res.ResourceLoader
|
||||
import org.jetbrains.jewel.foundation.lazy.SelectableLazyItemScope
|
||||
@@ -52,14 +51,10 @@ fun <T> LazyTree(
|
||||
onElementDoubleClick = onElementDoubleClick,
|
||||
interactionSource = interactionSource,
|
||||
keyActions = keyActions,
|
||||
chevronContent = { state ->
|
||||
Box(Modifier.rotate(if (state.isExpanded) 90f else 0f)) {
|
||||
Icon(
|
||||
painter = painterResource(style.icons.nodeChevron, resourceLoader),
|
||||
contentDescription = "Dropdown link",
|
||||
tint = colors.chevronTintFor(state).value
|
||||
)
|
||||
}
|
||||
chevronContent = { elementState ->
|
||||
val painterProvider = style.icons.nodeChevron(elementState.isExpanded)
|
||||
val painter by painterProvider.getPainter(elementState, resourceLoader)
|
||||
Icon(painter = painter, contentDescription = null)
|
||||
},
|
||||
nodeContent = {
|
||||
CompositionLocalProvider(
|
||||
@@ -68,12 +63,12 @@ fun <T> LazyTree(
|
||||
TreeElementState.of(
|
||||
isFocused,
|
||||
isSelected,
|
||||
false
|
||||
)
|
||||
false,
|
||||
),
|
||||
).value
|
||||
.takeOrElse { LocalContentColor.current }
|
||||
)
|
||||
),
|
||||
) { nodeContent(it) }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import org.jetbrains.jewel.styling.HorizontalProgressBarStyle
|
||||
|
||||
// TODO implement green/red/yellow variants based on com.intellij.openapi.progress.util.ColorProgressBar
|
||||
|
||||
@Composable
|
||||
fun HorizontalProgressBar(
|
||||
progress: Float, // from 0 to 1
|
||||
@@ -48,9 +50,9 @@ fun HorizontalProgressBar(
|
||||
color = colors.progress,
|
||||
topLeft = Offset(progressX, 0f),
|
||||
size = size.copy(width = progressWidth),
|
||||
cornerRadius = cornerRadius
|
||||
cornerRadius = cornerRadius,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -67,13 +69,13 @@ fun IndeterminateHorizontalProgressBar(
|
||||
targetValue = 2f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
tween(durationMillis = cycleDurationMillis, easing = LinearEasing),
|
||||
repeatMode = RepeatMode.Restart
|
||||
)
|
||||
repeatMode = RepeatMode.Restart,
|
||||
),
|
||||
)
|
||||
|
||||
val highlightWidth = style.metrics.indeterminateHighlightWidth
|
||||
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)
|
||||
|
||||
Box(
|
||||
@@ -90,9 +92,9 @@ fun IndeterminateHorizontalProgressBar(
|
||||
colors = colorsList,
|
||||
start = Offset(x, 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.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.input.InputMode
|
||||
import androidx.compose.ui.platform.LocalInputModeManager
|
||||
import androidx.compose.ui.res.ResourceLoader
|
||||
@@ -85,7 +86,7 @@ fun Link(
|
||||
indication = indication,
|
||||
style = style,
|
||||
resourceLoader = resourceLoader,
|
||||
icon = null
|
||||
icon = null,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -125,7 +126,7 @@ fun ExternalLink(
|
||||
indication = indication,
|
||||
style = style,
|
||||
resourceLoader = resourceLoader,
|
||||
icon = style.icons.externalLink
|
||||
icon = style.icons.externalLink,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -156,7 +157,7 @@ fun DropdownLink(
|
||||
Box(
|
||||
Modifier.onHover {
|
||||
hovered = it
|
||||
}
|
||||
},
|
||||
) {
|
||||
LinkImpl(
|
||||
text = text,
|
||||
@@ -180,7 +181,7 @@ fun DropdownLink(
|
||||
indication = indication,
|
||||
style = style,
|
||||
icon = style.icons.dropdownChevron,
|
||||
resourceLoader = resourceLoader
|
||||
resourceLoader = resourceLoader,
|
||||
)
|
||||
|
||||
if (expanded) {
|
||||
@@ -195,7 +196,8 @@ fun DropdownLink(
|
||||
modifier = menuModifier,
|
||||
style = menuStyle,
|
||||
horizontalAlignment = Alignment.Start, // TODO no idea what goes here
|
||||
content = menuContent
|
||||
content = menuContent,
|
||||
resourceLoader = resourceLoader,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -262,8 +264,8 @@ private fun LinkImpl(
|
||||
lineHeight = lineHeight,
|
||||
fontFamily = fontFamily,
|
||||
fontStyle = fontStyle,
|
||||
letterSpacing = letterSpacing
|
||||
)
|
||||
letterSpacing = letterSpacing,
|
||||
),
|
||||
)
|
||||
|
||||
val clickable = Modifier.clickable(
|
||||
@@ -274,28 +276,28 @@ private fun LinkImpl(
|
||||
enabled = enabled,
|
||||
role = Role.Button,
|
||||
interactionSource = interactionSource,
|
||||
indication = indication
|
||||
indication = indication,
|
||||
)
|
||||
|
||||
val focusHaloModifier = Modifier.border(
|
||||
alignment = Stroke.Alignment.Outside,
|
||||
width = LocalGlobalMetrics.current.outlineWidth,
|
||||
color = LocalGlobalColors.current.outlines.focused,
|
||||
shape = RoundedCornerShape(style.metrics.focusHaloCornerSize)
|
||||
shape = RoundedCornerShape(style.metrics.focusHaloCornerSize),
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = modifier.then(clickable)
|
||||
.appendIf(linkState.isFocused) { focusHaloModifier },
|
||||
horizontalArrangement = Arrangement.spacedBy(style.metrics.textIconGap),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
BasicText(
|
||||
text = text,
|
||||
style = mergedStyle,
|
||||
overflow = overflow,
|
||||
softWrap = true,
|
||||
maxLines = 1
|
||||
maxLines = 1,
|
||||
)
|
||||
|
||||
if (icon != null) {
|
||||
@@ -304,7 +306,7 @@ private fun LinkImpl(
|
||||
iconPainter,
|
||||
contentDescription = null,
|
||||
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,
|
||||
pressed = pressed,
|
||||
hovered = hovered,
|
||||
active = active
|
||||
active = active,
|
||||
)
|
||||
|
||||
@Composable
|
||||
@@ -397,7 +399,7 @@ value class LinkState(val state: ULong) : FocusableComponentState {
|
||||
(if (focused) Focused else 0UL) or
|
||||
(if (pressed) Pressed 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.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.rememberScrollbarAdapter
|
||||
@@ -31,13 +33,17 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.focus.FocusManager
|
||||
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.InputModeManager
|
||||
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.LocalFocusManager
|
||||
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.unit.Density
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Popup
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
import org.jetbrains.jewel.CommonStateBitMask.Active
|
||||
import org.jetbrains.jewel.foundation.Stroke
|
||||
import org.jetbrains.jewel.foundation.border
|
||||
import org.jetbrains.jewel.foundation.onHover
|
||||
import org.jetbrains.jewel.styling.MenuItemColors
|
||||
import org.jetbrains.jewel.styling.MenuItemMetrics
|
||||
import org.jetbrains.jewel.styling.MenuStyle
|
||||
|
||||
@Composable
|
||||
internal fun MenuContent(
|
||||
resourceLoader: ResourceLoader,
|
||||
modifier: Modifier = Modifier,
|
||||
style: MenuStyle = IntelliJTheme.menuStyle,
|
||||
content: MenuScope.() -> Unit,
|
||||
@@ -80,28 +92,28 @@ internal fun MenuContent(
|
||||
elevation = style.metrics.shadowSize,
|
||||
shape = menuShape,
|
||||
ambientColor = colors.shadow,
|
||||
spotColor = colors.shadow
|
||||
spotColor = colors.shadow,
|
||||
)
|
||||
.border(Stroke.Alignment.Center, style.metrics.borderWidth, colors.border, menuShape)
|
||||
.background(colors.background, menuShape)
|
||||
.width(IntrinsicSize.Max)
|
||||
.onHover {
|
||||
localMenuManager.onHoveredChange(it)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(scrollState)
|
||||
.padding(style.metrics.contentPadding)
|
||||
.padding(style.metrics.contentPadding),
|
||||
) {
|
||||
items.forEach {
|
||||
when (it) {
|
||||
is MenuSelectableItem -> {
|
||||
MenuSelectableItem(
|
||||
MenuItem(
|
||||
selected = it.isSelected,
|
||||
onClick = it.onClick,
|
||||
enabled = it.isEnabled,
|
||||
content = it.content
|
||||
content = it.content,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -109,7 +121,8 @@ internal fun MenuContent(
|
||||
MenuSubmenuItem(
|
||||
enabled = it.isEnabled,
|
||||
submenu = it.submenu,
|
||||
content = it.content
|
||||
content = it.content,
|
||||
resourceLoader = resourceLoader,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -122,7 +135,7 @@ internal fun MenuContent(
|
||||
Box(modifier = Modifier.matchParentSize()) {
|
||||
VerticalScrollbar(
|
||||
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 MenuScope.divider() {
|
||||
fun MenuScope.separator() {
|
||||
passiveItem {
|
||||
MenuSeparator()
|
||||
}
|
||||
@@ -193,7 +206,7 @@ private fun (MenuScope.() -> Unit).asList() = buildList {
|
||||
override fun submenu(enabled: Boolean, submenu: MenuScope.() -> Unit, content: @Composable () -> Unit) {
|
||||
add(SubmenuItem(enabled, submenu, content))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -222,17 +235,19 @@ private data class SubmenuItem(
|
||||
@Composable
|
||||
fun MenuSeparator(
|
||||
modifier: Modifier = Modifier,
|
||||
style: MenuStyle = IntelliJTheme.menuStyle,
|
||||
metrics: MenuItemMetrics = IntelliJTheme.menuStyle.metrics.itemMetrics,
|
||||
colors: MenuItemColors = IntelliJTheme.menuStyle.colors.itemColors,
|
||||
) {
|
||||
Divider(
|
||||
modifier = modifier.padding(style.metrics.itemMetrics.separatorPadding),
|
||||
modifier = modifier.padding(metrics.separatorPadding),
|
||||
thickness = metrics.separatorThickness,
|
||||
orientation = Orientation.Horizontal,
|
||||
color = style.colors.itemColors.separator
|
||||
color = colors.separator,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MenuSelectableItem(
|
||||
fun MenuItem(
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -283,9 +298,9 @@ fun MenuSelectableItem(
|
||||
enabled = enabled,
|
||||
role = Role.Button,
|
||||
interactionSource = interactionSource,
|
||||
indication = null
|
||||
indication = null,
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
DisposableEffect(Unit) {
|
||||
if (selected) {
|
||||
@@ -295,20 +310,19 @@ fun MenuSelectableItem(
|
||||
onDispose { }
|
||||
}
|
||||
|
||||
val colors = style.colors.itemColors
|
||||
val metrics = style.metrics
|
||||
val shape = RoundedCornerShape(metrics.itemMetrics.cornerSize)
|
||||
val itemColors = style.colors.itemColors
|
||||
val itemMetrics = style.metrics.itemMetrics
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides colors.contentFor(itemState).value
|
||||
LocalContentColor provides itemColors.contentFor(itemState).value,
|
||||
) {
|
||||
Row(
|
||||
val backgroundColor by itemColors.backgroundFor(itemState)
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.padding(metrics.itemMetrics.padding)
|
||||
.background(colors.backgroundFor(itemState).value, shape)
|
||||
.fillMaxWidth()
|
||||
.padding(metrics.itemMetrics.contentPadding),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
.drawItemBackground(itemMetrics, backgroundColor)
|
||||
.padding(itemMetrics.contentPadding),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
@@ -318,6 +332,7 @@ fun MenuSelectableItem(
|
||||
|
||||
@Composable
|
||||
fun MenuSubmenuItem(
|
||||
resourceLoader: ResourceLoader,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
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(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.drawItemBackground(menuMetrics.itemMetrics, backgroundColor)
|
||||
.focusRequester(focusRequester)
|
||||
.clickable(
|
||||
onClick = {
|
||||
@@ -362,7 +383,7 @@ fun MenuSubmenuItem(
|
||||
enabled = enabled,
|
||||
role = Role.Button,
|
||||
interactionSource = interactionSource,
|
||||
indication = null
|
||||
indication = null,
|
||||
)
|
||||
.onKeyEvent {
|
||||
if (it.type == KeyEventType.KeyDown && it.key == Key.DirectionRight) {
|
||||
@@ -371,34 +392,27 @@ fun MenuSubmenuItem(
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
.fillMaxWidth()
|
||||
},
|
||||
) {
|
||||
val colors = style.colors.itemColors
|
||||
val metrics = style.metrics
|
||||
val shape = RoundedCornerShape(metrics.itemMetrics.cornerSize)
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides colors.contentFor(itemState).value
|
||||
LocalContentColor provides itemColors.contentFor(itemState).value,
|
||||
) {
|
||||
Row(
|
||||
Modifier
|
||||
.padding(metrics.itemMetrics.padding)
|
||||
.background(colors.backgroundFor(itemState).value, shape)
|
||||
.fillMaxWidth()
|
||||
.padding(metrics.submenuMetrics.itemPadding),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
Modifier.fillMaxWidth()
|
||||
.padding(menuMetrics.itemMetrics.contentPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(Modifier.weight(1f)) {
|
||||
content()
|
||||
}
|
||||
|
||||
Box(Modifier.width(24.dp), contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
painter = painterResource(style.icons.submenuChevron),
|
||||
tint = colors.iconTintFor(itemState).value
|
||||
)
|
||||
}
|
||||
val chevronPainter by style.icons.submenuChevron.getPainter(itemState, resourceLoader)
|
||||
Icon(
|
||||
painter = chevronPainter,
|
||||
tint = itemColors.iconTintFor(itemState).value,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,14 +427,42 @@ fun MenuSubmenuItem(
|
||||
}
|
||||
},
|
||||
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
|
||||
internal fun Submenu(
|
||||
resourceLoader: ResourceLoader,
|
||||
onDismissRequest: (InputMode) -> Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
style: MenuStyle = IntelliJTheme.menuStyle,
|
||||
@@ -430,9 +472,9 @@ internal fun Submenu(
|
||||
|
||||
val popupPositionProvider = AnchorHorizontalMenuPositionProvider(
|
||||
contentOffset = style.metrics.submenuMetrics.offset,
|
||||
contentMargin = style.metrics.margin,
|
||||
contentMargin = style.metrics.menuMargin,
|
||||
alignment = Alignment.Top,
|
||||
density = density
|
||||
density = density,
|
||||
)
|
||||
|
||||
var focusManager: FocusManager? by mutableStateOf(null)
|
||||
@@ -443,16 +485,17 @@ internal fun Submenu(
|
||||
}
|
||||
|
||||
Popup(
|
||||
focusable = true,
|
||||
popupPositionProvider = popupPositionProvider,
|
||||
onDismissRequest = {
|
||||
menuManager.closeAll(InputMode.Touch, false)
|
||||
},
|
||||
popupPositionProvider = popupPositionProvider,
|
||||
properties = PopupProperties(focusable = true),
|
||||
onPreviewKeyEvent = { false },
|
||||
onKeyEvent = {
|
||||
val currentFocusManager = checkNotNull(focusManager) { "FocusManager must not be null" }
|
||||
val currentInputModeManager = checkNotNull(inputModeManager) { "InputModeManager must not be null" }
|
||||
handlePopupMenuOnKeyEvent(it, currentFocusManager, currentInputModeManager, menuManager)
|
||||
}
|
||||
},
|
||||
) {
|
||||
focusManager = LocalFocusManager.current
|
||||
inputModeManager = LocalInputModeManager.current
|
||||
@@ -460,7 +503,8 @@ internal fun Submenu(
|
||||
CompositionLocalProvider(LocalMenuManager provides menuManager) {
|
||||
MenuContent(
|
||||
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
|
||||
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
@@ -29,10 +28,10 @@ enum class Outline {
|
||||
@Composable
|
||||
fun Modifier.focusOutline(
|
||||
state: FocusableComponentState,
|
||||
outlineShape: Shape = RoundedCornerShape(LocalGlobalMetrics.current.outlineCornerSize),
|
||||
outlineWidth: Dp = LocalGlobalMetrics.current.outlineWidth,
|
||||
outlineShape: Shape,
|
||||
outlineWidth: Dp = IntelliJTheme.globalMetrics.outlineWidth,
|
||||
): Modifier {
|
||||
val outlineColors = LocalGlobalColors.current.outlines
|
||||
val outlineColors = IntelliJTheme.globalColors.outlines
|
||||
|
||||
return thenIf(state.isFocused) {
|
||||
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
|
||||
fun Modifier.outline(
|
||||
state: FocusableComponentState,
|
||||
outline: Outline,
|
||||
outlineShape: Shape,
|
||||
alignment: Stroke.Alignment = Stroke.Alignment.Outside,
|
||||
outlineShape: Shape = RoundedCornerShape(LocalGlobalMetrics.current.outlineCornerSize),
|
||||
outlineWidth: Dp = LocalGlobalMetrics.current.outlineWidth,
|
||||
outlineWidth: Dp = IntelliJTheme.globalMetrics.outlineWidth,
|
||||
): Modifier {
|
||||
val outlineColors = LocalGlobalColors.current.outlines
|
||||
val outlineColors = IntelliJTheme.globalColors.outlines
|
||||
|
||||
return thenIf(outline != Outline.None) {
|
||||
val outlineColor = when {
|
||||
|
||||
@@ -83,7 +83,7 @@ internal data class AnchorVerticalMenuPositionProvider(
|
||||
left = leftMargin,
|
||||
top = topMargin,
|
||||
right = windowSize.width - rightMargin,
|
||||
bottom = windowSize.height - bottomMargin
|
||||
bottom = windowSize.height - bottomMargin,
|
||||
)
|
||||
|
||||
// The content offset specified using the dropdown offset parameter.
|
||||
@@ -136,7 +136,7 @@ internal data class AnchorHorizontalMenuPositionProvider(
|
||||
left = leftMargin,
|
||||
top = topMargin,
|
||||
right = windowSize.width - rightMargin,
|
||||
bottom = windowSize.height - bottomMargin
|
||||
bottom = windowSize.height - bottomMargin,
|
||||
)
|
||||
|
||||
// The content offset specified using the dropdown offset parameter.
|
||||
|
||||
@@ -58,7 +58,7 @@ fun RadioButton(
|
||||
interactionSource = interactionSource,
|
||||
style = style,
|
||||
textStyle = textStyle,
|
||||
content = null
|
||||
content = null,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ fun RadioButtonRow(
|
||||
resourceLoader = resourceLoader,
|
||||
interactionSource = interactionSource,
|
||||
style = style,
|
||||
textStyle = textStyle
|
||||
textStyle = textStyle,
|
||||
) {
|
||||
Text(text)
|
||||
}
|
||||
@@ -113,7 +113,7 @@ fun RadioButtonRow(
|
||||
style = style,
|
||||
textStyle = textStyle,
|
||||
resourceLoader = resourceLoader,
|
||||
content = content
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ private fun RadioButtonImpl(
|
||||
enabled = enabled,
|
||||
role = Role.RadioButton,
|
||||
interactionSource = interactionSource,
|
||||
indication = null
|
||||
indication = null,
|
||||
)
|
||||
|
||||
val colors = style.colors
|
||||
@@ -174,14 +174,14 @@ private fun RadioButtonImpl(
|
||||
Row(
|
||||
wrapperModifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(metrics.iconContentGap),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
RadioButtonImage(Modifier, radioButtonPainter, radioButtonModifier)
|
||||
|
||||
val contentColor by colors.contentFor(radioButtonState)
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides textStyle.copy(color = contentColor.takeOrElse { textStyle.color }),
|
||||
LocalContentColor provides contentColor.takeOrElse { textStyle.color }
|
||||
LocalContentColor provides contentColor.takeOrElse { textStyle.color },
|
||||
) {
|
||||
content()
|
||||
}
|
||||
@@ -238,7 +238,7 @@ value class RadioButtonState(val state: ULong) : SelectableComponentState {
|
||||
focused = focused,
|
||||
pressed = pressed,
|
||||
hovered = hovered,
|
||||
active = active
|
||||
active = active,
|
||||
)
|
||||
|
||||
override fun toString() =
|
||||
@@ -264,7 +264,7 @@ value class RadioButtonState(val state: ULong) : SelectableComponentState {
|
||||
(if (focused) Focused else 0UL) or
|
||||
(if (pressed) Pressed 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,
|
||||
hoverDurationMillis = hoverDurationMillis,
|
||||
unhoverColor = style.colors.thumbBackground,
|
||||
hoverColor = style.colors.thumbBackgroundHovered
|
||||
)
|
||||
hoverColor = style.colors.thumbBackgroundHovered,
|
||||
),
|
||||
) {
|
||||
VerticalScrollbar(
|
||||
adapter = adapter,
|
||||
modifier = modifier.padding(style.metrics.trackPadding),
|
||||
reverseLayout = reverseLayout,
|
||||
style = LocalScrollbarStyle.current,
|
||||
interactionSource = interactionSource
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -67,15 +67,15 @@ fun HorizontalScrollbar(
|
||||
shape = shape,
|
||||
hoverDurationMillis = hoverDurationMillis,
|
||||
unhoverColor = style.colors.thumbBackground,
|
||||
hoverColor = style.colors.thumbBackgroundHovered
|
||||
)
|
||||
hoverColor = style.colors.thumbBackgroundHovered,
|
||||
),
|
||||
) {
|
||||
HorizontalScrollbar(
|
||||
adapter = adapter,
|
||||
modifier = modifier.padding(style.metrics.trackPadding),
|
||||
reverseLayout = reverseLayout,
|
||||
style = LocalScrollbarStyle.current,
|
||||
interactionSource = interactionSource
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -98,15 +98,15 @@ fun TabStripHorizontalScrollbar(
|
||||
shape = shape,
|
||||
hoverDurationMillis = hoverDurationMillis,
|
||||
unhoverColor = style.colors.thumbBackground,
|
||||
hoverColor = style.colors.thumbBackgroundHovered
|
||||
)
|
||||
hoverColor = style.colors.thumbBackgroundHovered,
|
||||
),
|
||||
) {
|
||||
HorizontalScrollbar(
|
||||
adapter = adapter,
|
||||
modifier = modifier.padding(1.dp),
|
||||
reverseLayout = reverseLayout,
|
||||
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(
|
||||
modifier
|
||||
.focusable(true, remember { MutableInteractionSource() })
|
||||
.onHover { tabStripState = tabStripState.copy(hovered = it) }
|
||||
.onHover { tabStripState = tabStripState.copy(hovered = it) },
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -52,12 +52,12 @@ fun TabStrip(
|
||||
reverseDirection = ScrollableDefaults.reverseDirection(
|
||||
LocalLayoutDirection.current,
|
||||
Orientation.Vertical,
|
||||
false
|
||||
false,
|
||||
),
|
||||
state = scrollState,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
)
|
||||
.selectableGroup()
|
||||
.selectableGroup(),
|
||||
) {
|
||||
tabs.forEach {
|
||||
TabImpl(isActive = tabStripState.isActive, tabData = it)
|
||||
@@ -69,20 +69,20 @@ fun TabStrip(
|
||||
animationSpec = tween(
|
||||
durationMillis = 125,
|
||||
delayMillis = 0,
|
||||
easing = LinearEasing
|
||||
)
|
||||
easing = LinearEasing,
|
||||
),
|
||||
),
|
||||
exit = fadeOut(
|
||||
animationSpec = tween(
|
||||
durationMillis = 125,
|
||||
delayMillis = 700,
|
||||
easing = LinearEasing
|
||||
)
|
||||
)
|
||||
easing = LinearEasing,
|
||||
),
|
||||
),
|
||||
) {
|
||||
TabStripHorizontalScrollbar(
|
||||
adapter = rememberScrollbarAdapter(scrollState),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -208,7 +208,7 @@ value class TabStripState(val state: ULong) : FocusableComponentState {
|
||||
focused = focused,
|
||||
pressed = pressed,
|
||||
hovered = hovered,
|
||||
active = active
|
||||
active = active,
|
||||
)
|
||||
|
||||
override fun toString() =
|
||||
@@ -232,7 +232,7 @@ value class TabStripState(val state: ULong) : FocusableComponentState {
|
||||
(if (pressed) CommonStateBitMask.Pressed else 0UL) or
|
||||
(if (warning) CommonStateBitMask.Warning 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(
|
||||
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 iconAlpha by tabStyle.contentAlpha.iconFor(tabState)
|
||||
@@ -92,7 +92,7 @@ internal fun TabImpl(
|
||||
selected = tabData.selected,
|
||||
interactionSource = interactionSource,
|
||||
indication = NoIndication,
|
||||
role = Role.Tab
|
||||
role = Role.Tab,
|
||||
)
|
||||
.drawBehind {
|
||||
val strokeThickness = lineThickness.toPx()
|
||||
@@ -105,12 +105,12 @@ internal fun TabImpl(
|
||||
start = Offset(0 + capDxFix, startY),
|
||||
end = Offset(endX - capDxFix, startY),
|
||||
strokeWidth = strokeThickness,
|
||||
cap = StrokeCap.Round
|
||||
cap = StrokeCap.Round,
|
||||
)
|
||||
}
|
||||
.padding(tabStyle.metrics.tabPadding),
|
||||
horizontalArrangement = Arrangement.spacedBy(tabStyle.metrics.closeContentGap),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
tabData.tabIconResource?.let { icon ->
|
||||
val iconPainter = painterResource(icon, LocalResourceLoader.current)
|
||||
@@ -120,7 +120,7 @@ internal fun TabImpl(
|
||||
Text(
|
||||
modifier = Modifier.alpha(labelAlpha),
|
||||
text = tabData.label,
|
||||
color = tabStyle.colors.contentFor(tabState).value
|
||||
color = tabStyle.colors.contentFor(tabState).value,
|
||||
)
|
||||
val showCloseIcon = when (tabData) {
|
||||
is TabData.Default -> tabData.closable
|
||||
@@ -147,10 +147,10 @@ internal fun TabImpl(
|
||||
interactionSource = closeActionInteractionSource,
|
||||
indication = null,
|
||||
onClick = tabData.onClose,
|
||||
role = Role.Button
|
||||
role = Role.Button,
|
||||
).size(16.dp),
|
||||
painter = closePainter,
|
||||
contentDescription = "Close tab ${tabData.label}"
|
||||
contentDescription = "Close tab ${tabData.label}",
|
||||
)
|
||||
} else if (tabData.closable) {
|
||||
Spacer(Modifier.size(16.dp))
|
||||
@@ -215,7 +215,7 @@ value class TabState(val state: ULong) : SelectableComponentState {
|
||||
focused = focused,
|
||||
pressed = pressed,
|
||||
hovered = hovered,
|
||||
active = active
|
||||
active = active,
|
||||
)
|
||||
|
||||
override fun toString() =
|
||||
@@ -237,7 +237,7 @@ value class TabState(val state: ULong) : SelectableComponentState {
|
||||
(if (focused) Focused else 0UL) or
|
||||
(if (pressed) Pressed 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.InlineTextContent
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -36,7 +35,7 @@ fun Text(
|
||||
softWrap: Boolean = true,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
style: TextStyle = IntelliJTheme.defaultTextStyle,
|
||||
) {
|
||||
Text(
|
||||
AnnotatedString(text),
|
||||
@@ -55,7 +54,7 @@ fun Text(
|
||||
maxLines,
|
||||
emptyMap(),
|
||||
onTextLayout,
|
||||
style
|
||||
style,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -77,7 +76,7 @@ fun Text(
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
inlineContent: Map<String, InlineTextContent> = emptyMap(),
|
||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
style: TextStyle = IntelliJTheme.defaultTextStyle,
|
||||
) {
|
||||
val textColor = color.takeOrElse {
|
||||
style.color.takeOrElse {
|
||||
@@ -97,8 +96,8 @@ fun Text(
|
||||
fontFamily = fontFamily,
|
||||
textDecoration = textDecoration,
|
||||
fontStyle = fontStyle,
|
||||
letterSpacing = letterSpacing
|
||||
)
|
||||
letterSpacing = letterSpacing,
|
||||
),
|
||||
)
|
||||
BasicText(text, modifier, mergedStyle, onTextLayout, overflow, softWrap, maxLines, minLines = 1, inlineContent)
|
||||
}
|
||||
@@ -110,9 +109,3 @@ val LocalTextStyle = staticCompositionLocalOf<TextStyle> {
|
||||
val LocalContentColor = staticCompositionLocalOf<Color> {
|
||||
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.PaddingValues
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
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.VisualTransformation
|
||||
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 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
|
||||
fun TextArea(
|
||||
value: String,
|
||||
@@ -37,9 +37,8 @@ fun TextArea(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
isError: Boolean = false,
|
||||
outline: Outline = Outline.None,
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
hint: @Composable (() -> Unit)? = null,
|
||||
undecorated: Boolean = false,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
@@ -69,9 +68,8 @@ fun TextArea(
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
isError = isError,
|
||||
outline = outline,
|
||||
placeholder = placeholder,
|
||||
hint = hint,
|
||||
undecorated = undecorated,
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
@@ -80,10 +78,14 @@ fun TextArea(
|
||||
onTextLayout = onTextLayout,
|
||||
style = style,
|
||||
textStyle = textStyle,
|
||||
interactionSource = interactionSource
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param placeholder the optional placeholder to be displayed over the
|
||||
* component when the [value] is empty.
|
||||
*/
|
||||
@Composable
|
||||
fun TextArea(
|
||||
value: TextFieldValue,
|
||||
@@ -92,9 +94,8 @@ fun TextArea(
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
hint: @Composable (() -> Unit)? = null,
|
||||
undecorated: Boolean = false,
|
||||
isError: Boolean = false,
|
||||
outline: Outline = Outline.None,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions(),
|
||||
@@ -104,13 +105,15 @@ fun TextArea(
|
||||
textStyle: TextStyle = IntelliJTheme.defaultTextStyle,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
) {
|
||||
val minSize = style.metrics.minSize
|
||||
InputField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
modifier = modifier
|
||||
.defaultMinSize(minWidth = minSize.width, minHeight = minSize.height),
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
isError = isError,
|
||||
outline = outline,
|
||||
undecorated = undecorated,
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
@@ -120,108 +123,89 @@ fun TextArea(
|
||||
onTextLayout = onTextLayout,
|
||||
style = style,
|
||||
textStyle = textStyle,
|
||||
interactionSource = interactionSource
|
||||
interactionSource = interactionSource,
|
||||
) { innerTextField, state ->
|
||||
val minSize = style.metrics.minSize
|
||||
|
||||
TextAreaDecorationBox(
|
||||
modifier = Modifier
|
||||
.defaultMinSize(minWidth = minSize.width, minHeight = minSize.height),
|
||||
innerTextField = innerTextField,
|
||||
contentPadding = style.metrics.contentPadding,
|
||||
placeholderTextColor = style.colors.placeholder,
|
||||
placeholder = if (value.text.isEmpty()) placeholder else null,
|
||||
hintTextStyle = style.hintTextStyle,
|
||||
hintTextColor = style.colors.hintContentFor(state).value,
|
||||
hint = hint
|
||||
textStyle = textStyle,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextAreaDecorationBox(
|
||||
modifier: Modifier = Modifier,
|
||||
innerTextField: @Composable () -> Unit,
|
||||
contentPadding: PaddingValues,
|
||||
textStyle: TextStyle,
|
||||
placeholderTextColor: Color,
|
||||
placeholder: @Composable (() -> Unit)?,
|
||||
hintTextStyle: TextStyle,
|
||||
hintTextColor: Color,
|
||||
hint: @Composable (() -> Unit)?,
|
||||
) {
|
||||
Layout(
|
||||
modifier = modifier,
|
||||
content = {
|
||||
if (placeholder != null) {
|
||||
Box(modifier = Modifier.layoutId(PLACEHOLDER_ID), contentAlignment = Alignment.Center) {
|
||||
Box(
|
||||
modifier = Modifier.layoutId(PLACEHOLDER_ID),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides textStyle.copy(color = 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()
|
||||
}
|
||||
|
||||
if (hint != null) {
|
||||
Box(Modifier.layoutId(HINT_ID).fillMaxWidth()) {
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides hintTextStyle,
|
||||
LocalContentColor provides hintTextColor,
|
||||
content = hint
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) { measurables, incomingConstraints ->
|
||||
val horizontalPadding =
|
||||
(contentPadding.calculateLeftPadding(layoutDirection) + contentPadding.calculateRightPadding(layoutDirection)).roundToPx()
|
||||
(
|
||||
contentPadding.calculateLeftPadding(layoutDirection) +
|
||||
contentPadding.calculateRightPadding(layoutDirection)
|
||||
).roundToPx()
|
||||
val verticalPadding =
|
||||
(contentPadding.calculateTopPadding() + contentPadding.calculateBottomPadding()).roundToPx()
|
||||
(
|
||||
contentPadding.calculateTopPadding() +
|
||||
contentPadding.calculateBottomPadding()
|
||||
).roundToPx()
|
||||
|
||||
// measure hint
|
||||
val hintConstraints = incomingConstraints.copy(minHeight = 0)
|
||||
val hintPlaceable = measurables.find { it.layoutId == HINT_ID }?.measure(hintConstraints)
|
||||
val occupiedSpaceVertically = hintPlaceable?.height ?: 0
|
||||
val textAreaConstraints = incomingConstraints.offset(
|
||||
horizontal = -horizontalPadding,
|
||||
vertical = -verticalPadding,
|
||||
).copy(minHeight = 0)
|
||||
|
||||
val constraintsWithoutPadding = incomingConstraints.offset(
|
||||
-horizontalPadding,
|
||||
-verticalPadding - occupiedSpaceVertically
|
||||
)
|
||||
val textAreaPlaceable = measurables.first { it.layoutId == TEXT_AREA_ID }.measure(textAreaConstraints)
|
||||
|
||||
val textConstraints = constraintsWithoutPadding
|
||||
val textFieldPlaceable = measurables.first { it.layoutId == TEXT_FIELD_ID }.measure(textConstraints)
|
||||
|
||||
// measure placeholder
|
||||
val placeholderConstraints = textConstraints.copy(minWidth = 0, minHeight = 0)
|
||||
// Measure placeholder
|
||||
val placeholderConstraints = textAreaConstraints.copy(minWidth = 0, minHeight = 0)
|
||||
val placeholderPlaceable = measurables.find { it.layoutId == PLACEHOLDER_ID }?.measure(placeholderConstraints)
|
||||
|
||||
val width = calculateWidth(
|
||||
textFieldPlaceable,
|
||||
textAreaPlaceable,
|
||||
placeholderPlaceable,
|
||||
horizontalPadding,
|
||||
hintPlaceable,
|
||||
constraintsWithoutPadding
|
||||
textAreaConstraints,
|
||||
)
|
||||
val height = calculateHeight(
|
||||
textFieldPlaceable,
|
||||
textAreaPlaceable,
|
||||
placeholderPlaceable,
|
||||
verticalPadding,
|
||||
hintPlaceable,
|
||||
constraintsWithoutPadding
|
||||
textAreaConstraints,
|
||||
)
|
||||
|
||||
layout(width, height) {
|
||||
place(
|
||||
height,
|
||||
contentPadding,
|
||||
hintPlaceable,
|
||||
textFieldPlaceable,
|
||||
textAreaPlaceable,
|
||||
placeholderPlaceable,
|
||||
layoutDirection,
|
||||
this@Layout
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -230,55 +214,44 @@ private fun TextAreaDecorationBox(
|
||||
private fun calculateWidth(
|
||||
textFieldPlaceable: Placeable,
|
||||
placeholderPlaceable: Placeable?,
|
||||
horizontalPadding: Int,
|
||||
hintPlaceable: Placeable?,
|
||||
constraints: Constraints,
|
||||
): Int {
|
||||
return maxOf(
|
||||
textFieldPlaceable.width + horizontalPadding,
|
||||
(placeholderPlaceable?.width ?: 0) + horizontalPadding,
|
||||
hintPlaceable?.width ?: 0,
|
||||
constraints.minWidth
|
||||
): Int =
|
||||
maxOf(
|
||||
textFieldPlaceable.width,
|
||||
placeholderPlaceable?.width ?: 0,
|
||||
)
|
||||
}
|
||||
.coerceAtLeast(constraints.minWidth)
|
||||
|
||||
private fun calculateHeight(
|
||||
textFieldPlaceable: Placeable,
|
||||
placeholderPlaceable: Placeable?,
|
||||
verticalPadding: Int,
|
||||
hintPlaceable: Placeable?,
|
||||
constraints: Constraints,
|
||||
): Int {
|
||||
val middleSection = maxOf(
|
||||
val textAreaHeight = maxOf(
|
||||
textFieldPlaceable.height,
|
||||
placeholderPlaceable?.height ?: 0
|
||||
) + verticalPadding
|
||||
val wrappedHeight = (hintPlaceable?.height ?: 0) + middleSection
|
||||
return max(wrappedHeight, constraints.minHeight)
|
||||
placeholderPlaceable?.height ?: 0,
|
||||
)
|
||||
return (textAreaHeight + verticalPadding).coerceAtLeast(constraints.minHeight)
|
||||
}
|
||||
|
||||
private fun Placeable.PlacementScope.place(
|
||||
height: Int,
|
||||
contentPadding: PaddingValues,
|
||||
hintPlaceable: Placeable?,
|
||||
textFieldPlaceable: Placeable,
|
||||
textAreaPlaceable: Placeable,
|
||||
placeholderPlaceable: Placeable?,
|
||||
layoutDirection: LayoutDirection,
|
||||
density: Density,
|
||||
) = with(density) {
|
||||
hintPlaceable?.placeRelative(
|
||||
) {
|
||||
// placed center vertically
|
||||
textAreaPlaceable.placeRelative(
|
||||
0,
|
||||
height - hintPlaceable.height
|
||||
Alignment.CenterVertically.align(textAreaPlaceable.height, height),
|
||||
)
|
||||
|
||||
val y = contentPadding.calculateTopPadding().roundToPx()
|
||||
val x = contentPadding.calculateLeftPadding(layoutDirection).roundToPx()
|
||||
|
||||
textFieldPlaceable.placeRelative(x, y)
|
||||
|
||||
placeholderPlaceable?.placeRelative(x, y)
|
||||
// placed similar to the input text above
|
||||
placeholderPlaceable?.placeRelative(
|
||||
0,
|
||||
Alignment.CenterVertically.align(placeholderPlaceable.height, height),
|
||||
)
|
||||
}
|
||||
|
||||
private const val PLACEHOLDER_ID = "Placeholder"
|
||||
private const val TEXT_FIELD_ID = "TextField"
|
||||
private const val HINT_ID = "Hint"
|
||||
private const val TEXT_AREA_ID = "TextField"
|
||||
|
||||
@@ -23,11 +23,14 @@ import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.offset
|
||||
import org.jetbrains.jewel.styling.TextFieldStyle
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* @param placeholder the optional placeholder to be displayed over the
|
||||
* component when the [value] is empty.
|
||||
*/
|
||||
@Composable
|
||||
fun TextField(
|
||||
value: String,
|
||||
@@ -35,7 +38,7 @@ fun TextField(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
isError: Boolean = false,
|
||||
outline: Outline = Outline.None,
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
undecorated: Boolean = false,
|
||||
@@ -65,7 +68,7 @@ fun TextField(
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
isError = isError,
|
||||
outline = outline,
|
||||
placeholder = placeholder,
|
||||
trailingIcon = trailingIcon,
|
||||
undecorated = undecorated,
|
||||
@@ -74,10 +77,14 @@ fun TextField(
|
||||
keyboardActions = keyboardActions,
|
||||
onTextLayout = onTextLayout,
|
||||
style = style,
|
||||
interactionSource = interactionSource
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param placeholder the optional placeholder to be displayed over the
|
||||
* component when the [value] is empty.
|
||||
*/
|
||||
@Composable
|
||||
fun TextField(
|
||||
value: TextFieldValue,
|
||||
@@ -85,7 +92,7 @@ fun TextField(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
isError: Boolean = false,
|
||||
outline: Outline = Outline.None,
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
undecorated: Boolean = false,
|
||||
@@ -103,7 +110,7 @@ fun TextField(
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
isError = isError,
|
||||
outline = outline,
|
||||
undecorated = undecorated,
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
@@ -113,17 +120,18 @@ fun TextField(
|
||||
onTextLayout = onTextLayout,
|
||||
style = style,
|
||||
textStyle = textStyle,
|
||||
interactionSource = interactionSource
|
||||
interactionSource = interactionSource,
|
||||
) { innerTextField, _ ->
|
||||
val minSize = style.metrics.minSize
|
||||
|
||||
TextFieldDecorationBox(
|
||||
modifier = Modifier.defaultMinSize(minHeight = minSize.width, minWidth = minSize.height)
|
||||
modifier = Modifier.defaultMinSize(minWidth = minSize.width, minHeight = minSize.height)
|
||||
.padding(style.metrics.contentPadding),
|
||||
innerTextField = innerTextField,
|
||||
textStyle = textStyle,
|
||||
placeholderTextColor = style.colors.placeholder,
|
||||
placeholder = if (value.text.isEmpty()) placeholder else null,
|
||||
trailingIcon = trailingIcon
|
||||
trailingIcon = trailingIcon,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -132,6 +140,7 @@ fun TextField(
|
||||
private fun TextFieldDecorationBox(
|
||||
modifier: Modifier = Modifier,
|
||||
innerTextField: @Composable () -> Unit,
|
||||
textStyle: TextStyle,
|
||||
placeholderTextColor: Color,
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
@@ -147,8 +156,9 @@ private fun TextFieldDecorationBox(
|
||||
if (placeholder != null) {
|
||||
Box(modifier = Modifier.layoutId(PLACEHOLDER_ID), contentAlignment = Alignment.Center) {
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides textStyle.copy(color = placeholderTextColor),
|
||||
LocalContentColor provides placeholderTextColor,
|
||||
content = placeholder
|
||||
content = placeholder,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -156,40 +166,37 @@ private fun TextFieldDecorationBox(
|
||||
Box(modifier = Modifier.layoutId(TEXT_FIELD_ID), propagateMinConstraints = true) {
|
||||
innerTextField()
|
||||
}
|
||||
}
|
||||
},
|
||||
) { measurables, incomingConstraints ->
|
||||
// used to calculate the constraints for measuring elements that will be placed in a row
|
||||
var occupiedSpaceHorizontally = 0
|
||||
|
||||
val constraintsWithoutPadding = incomingConstraints
|
||||
|
||||
val iconsConstraints = constraintsWithoutPadding.copy(minWidth = 0, minHeight = 0)
|
||||
val iconConstraints = incomingConstraints.copy(minWidth = 0, minHeight = 0)
|
||||
|
||||
// measure trailing icon
|
||||
val trailingPlaceable = measurables.find { it.layoutId == TRAILING_ID }
|
||||
?.measure(iconsConstraints)
|
||||
?.measure(iconConstraints)
|
||||
occupiedSpaceHorizontally += trailingPlaceable?.width ?: 0
|
||||
|
||||
val textConstraints = constraintsWithoutPadding.offset(
|
||||
horizontal = -occupiedSpaceHorizontally
|
||||
val textFieldConstraints = incomingConstraints.offset(
|
||||
horizontal = -occupiedSpaceHorizontally,
|
||||
).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
|
||||
val placeholderConstraints = textConstraints.copy(minWidth = 0)
|
||||
val placeholderConstraints = textFieldConstraints.copy(minWidth = 0)
|
||||
val placeholderPlaceable = measurables.find { it.layoutId == PLACEHOLDER_ID }?.measure(placeholderConstraints)
|
||||
|
||||
val width = calculateWidth(
|
||||
trailingPlaceable,
|
||||
textFieldPlaceable,
|
||||
placeholderPlaceable,
|
||||
incomingConstraints
|
||||
incomingConstraints,
|
||||
)
|
||||
val height = calculateHeight(
|
||||
trailingPlaceable,
|
||||
textFieldPlaceable,
|
||||
placeholderPlaceable,
|
||||
incomingConstraints
|
||||
incomingConstraints,
|
||||
)
|
||||
|
||||
layout(width, height) {
|
||||
@@ -199,7 +206,6 @@ private fun TextFieldDecorationBox(
|
||||
trailingPlaceable,
|
||||
textFieldPlaceable,
|
||||
placeholderPlaceable,
|
||||
this@Layout
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -213,7 +219,7 @@ private fun calculateWidth(
|
||||
): Int {
|
||||
val middleSection = maxOf(
|
||||
textFieldPlaceable.width,
|
||||
placeholderPlaceable?.width ?: 0
|
||||
placeholderPlaceable?.width ?: 0,
|
||||
)
|
||||
val wrappedWidth = middleSection + (trailingPlaceable?.width ?: 0)
|
||||
return max(wrappedWidth, constraints.minWidth)
|
||||
@@ -224,14 +230,12 @@ private fun calculateHeight(
|
||||
textFieldPlaceable: Placeable,
|
||||
placeholderPlaceable: Placeable?,
|
||||
constraints: Constraints,
|
||||
): Int {
|
||||
return maxOf(
|
||||
textFieldPlaceable.height,
|
||||
placeholderPlaceable?.height ?: 0,
|
||||
trailingPlaceable?.height ?: 0,
|
||||
constraints.minHeight
|
||||
)
|
||||
}
|
||||
): Int = maxOf(
|
||||
textFieldPlaceable.height,
|
||||
placeholderPlaceable?.height ?: 0,
|
||||
trailingPlaceable?.height ?: 0,
|
||||
constraints.minHeight,
|
||||
)
|
||||
|
||||
private fun Placeable.PlacementScope.place(
|
||||
height: Int,
|
||||
@@ -239,28 +243,24 @@ private fun Placeable.PlacementScope.place(
|
||||
trailingPlaceable: Placeable?,
|
||||
textFieldPlaceable: Placeable,
|
||||
placeholderPlaceable: Placeable?,
|
||||
density: Density,
|
||||
) = with(density) {
|
||||
) {
|
||||
// placed center vertically and to the end edge horizontally
|
||||
trailingPlaceable?.placeRelative(
|
||||
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 to the top with padding for multi line text field
|
||||
// placed center vertically
|
||||
textFieldPlaceable.placeRelative(
|
||||
0,
|
||||
Alignment.CenterVertically.align(textFieldPlaceable.height, height)
|
||||
Alignment.CenterVertically.align(textFieldPlaceable.height, height),
|
||||
)
|
||||
|
||||
// placed similar to the input text above
|
||||
placeholderPlaceable?.let {
|
||||
it.placeRelative(
|
||||
0,
|
||||
Alignment.CenterVertically.align(it.height, height)
|
||||
)
|
||||
}
|
||||
placeholderPlaceable?.placeRelative(
|
||||
0,
|
||||
Alignment.CenterVertically.align(placeholderPlaceable.height, height),
|
||||
)
|
||||
}
|
||||
|
||||
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.unit.Dp
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.isUnspecified
|
||||
import androidx.compose.ui.unit.takeOrElse
|
||||
import androidx.compose.ui.unit.toSize
|
||||
@@ -49,14 +50,26 @@ fun Modifier.border(stroke: Stroke, shape: Shape): Modifier = when (stroke) {
|
||||
width = stroke.width,
|
||||
brush = stroke.brush,
|
||||
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)
|
||||
|
||||
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) {
|
||||
// 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
|
||||
@@ -81,11 +94,13 @@ private fun Modifier.drawBorderWithAlignment(
|
||||
val strokeWidthPx = max(
|
||||
min(
|
||||
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)) {
|
||||
is Outline.Rectangle -> {
|
||||
when (shape) {
|
||||
@@ -95,19 +110,40 @@ private fun Modifier.drawBorderWithAlignment(
|
||||
Outline.Rounded(RoundRect(outline.rect)),
|
||||
brush,
|
||||
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 {
|
||||
@@ -122,7 +158,7 @@ private fun Modifier.drawBorderWithAlignment(
|
||||
}
|
||||
properties["shape"] = shape
|
||||
properties["expand"] = expand
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
private class BorderCache(
|
||||
@@ -151,7 +187,7 @@ private class BorderCache(
|
||||
targetImageBitmap = ImageBitmap(
|
||||
borderSize.width,
|
||||
borderSize.height,
|
||||
config = config
|
||||
config = config,
|
||||
).also {
|
||||
imageBitmap = it
|
||||
}
|
||||
@@ -166,12 +202,12 @@ private class BorderCache(
|
||||
this,
|
||||
layoutDirection,
|
||||
targetCanvas,
|
||||
drawSize
|
||||
drawSize,
|
||||
) {
|
||||
drawRect(
|
||||
color = Color.Black,
|
||||
size = drawSize,
|
||||
blendMode = BlendMode.Clear
|
||||
blendMode = BlendMode.Clear,
|
||||
)
|
||||
block()
|
||||
}
|
||||
@@ -219,26 +255,27 @@ private fun ContentDrawScope.drawRoundedBorder(
|
||||
strokeWidthPx: Float,
|
||||
expandWidthPx: Float,
|
||||
) {
|
||||
val rrect = when (alignment) {
|
||||
val roundRect = when (alignment) {
|
||||
Stroke.Alignment.Inside -> outline.roundRect.inflate(expandWidthPx - strokeWidthPx / 2f)
|
||||
Stroke.Alignment.Center -> outline.roundRect.inflate(expandWidthPx)
|
||||
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 borderPath = cache.obtainPath().apply {
|
||||
reset()
|
||||
fillType = PathFillType.EvenOdd
|
||||
addRoundRect(rrect.deflate(strokeWidthPx / 2f))
|
||||
addRoundRect(rrect.inflate(strokeWidthPx / 2f))
|
||||
addRoundRect(roundRect.deflate(strokeWidthPx / 2f))
|
||||
addRoundRect(roundRect.inflate(strokeWidthPx / 2f))
|
||||
}
|
||||
drawPath(borderPath, brush)
|
||||
} else {
|
||||
drawOutline(
|
||||
outline = Outline.Rounded(rrect),
|
||||
outline = Outline.Rounded(roundRect),
|
||||
brush = brush,
|
||||
style = DrawScopeStroke(strokeWidthPx)
|
||||
style = DrawScopeStroke(strokeWidthPx),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -276,10 +313,12 @@ private fun CacheDrawScope.drawGenericBorder(
|
||||
inner -> {
|
||||
return@onDrawWithContent
|
||||
}
|
||||
|
||||
-inner -> {
|
||||
// Samply draw the outline when abs(outer) and abs(inner) are the same
|
||||
drawOutline(outline, brush, style = DrawScopeStroke(outer * 2f))
|
||||
}
|
||||
|
||||
else -> {
|
||||
val config: ImageBitmapConfig
|
||||
val colorFilter: ColorFilter?
|
||||
@@ -300,13 +339,13 @@ private fun CacheDrawScope.drawGenericBorder(
|
||||
val cacheImageBitmap: ImageBitmap
|
||||
val pathBoundsSize = IntSize(
|
||||
ceil(pathBounds.width).toInt(),
|
||||
ceil(pathBounds.height).toInt()
|
||||
ceil(pathBounds.height).toInt(),
|
||||
)
|
||||
|
||||
with(borderCache) {
|
||||
cacheImageBitmap = drawBorderCache(
|
||||
pathBoundsSize,
|
||||
config
|
||||
config,
|
||||
) {
|
||||
translate(-pathBounds.left, -pathBounds.top) {
|
||||
if (inner < 0f && outer > 0f) {
|
||||
@@ -317,7 +356,12 @@ private fun CacheDrawScope.drawGenericBorder(
|
||||
drawPath(path = outline.path, brush = brush, style = DrawScopeStroke(outer * 2f))
|
||||
|
||||
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)
|
||||
@@ -327,7 +371,12 @@ private fun CacheDrawScope.drawGenericBorder(
|
||||
drawPath(path = outline.path, brush = brush, style = DrawScopeStroke(-inner * 2f))
|
||||
|
||||
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)
|
||||
@@ -11,7 +11,7 @@ internal fun RoundRect.inflate(delta: Float) = RoundRect(
|
||||
topLeftCornerRadius = CornerRadius(topLeftCornerRadius.x + delta, topLeftCornerRadius.y + delta),
|
||||
topRightCornerRadius = CornerRadius(topRightCornerRadius.x + delta, topRightCornerRadius.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(
|
||||
@@ -22,10 +22,10 @@ internal fun RoundRect.deflate(delta: Float) = RoundRect(
|
||||
topLeftCornerRadius = CornerRadius(topLeftCornerRadius.x - delta, topLeftCornerRadius.y - delta),
|
||||
topRightCornerRadius = CornerRadius(topRightCornerRadius.x - delta, topRightCornerRadius.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 ||
|
||||
topRightCornerRadius.x == 0f && topRightCornerRadius.y == 0f ||
|
||||
bottomLeftCornerRadius.x == 0f && bottomLeftCornerRadius.y == 0f ||
|
||||
@@ -141,8 +141,8 @@ open class DefaultSelectableOnKeyEvent(
|
||||
selectableState.addElementsToSelection(
|
||||
listOf(
|
||||
currentIndex,
|
||||
prevIndex
|
||||
)
|
||||
prevIndex,
|
||||
),
|
||||
)
|
||||
selectableState.lastKeyEventUsedMouse = true
|
||||
}
|
||||
@@ -170,8 +170,8 @@ open class DefaultSelectableOnKeyEvent(
|
||||
selectableState.addElementsToSelection(
|
||||
listOf(
|
||||
currentIndex,
|
||||
nextSelectableIndex
|
||||
)
|
||||
nextSelectableIndex,
|
||||
),
|
||||
)
|
||||
selectableState.lastKeyEventUsedMouse = true
|
||||
}
|
||||
@@ -68,7 +68,7 @@ fun SelectableLazyColumn(
|
||||
interactionSource = interactionSource,
|
||||
keyActions = keyActions.handleOnKeyEvent(rememberCoroutineScope()),
|
||||
pointerHandlingScopedActions = pointerHandlingScopedActions,
|
||||
content = content
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ internal fun BaseSelectableLazyColumn(
|
||||
reverseLayout = reverseLayout,
|
||||
verticalArrangement = verticalArrangement,
|
||||
horizontalAlignment = horizontalAlignment,
|
||||
flingBehavior = flingBehavior
|
||||
flingBehavior = flingBehavior,
|
||||
) {
|
||||
state.clearKeys()
|
||||
SelectableLazyListScopeContainer(state, pointerHandlingScopedActions).apply(content)
|
||||
@@ -149,7 +149,7 @@ internal class SelectableLazyListScopeContainer(
|
||||
Box(
|
||||
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))
|
||||
}
|
||||
@@ -170,7 +170,7 @@ internal class SelectableLazyListScopeContainer(
|
||||
} else {
|
||||
SelectableKey.NotFocusable(
|
||||
key(it),
|
||||
selectable(it)
|
||||
selectable(it),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -187,11 +187,11 @@ internal class SelectableLazyListScopeContainer(
|
||||
Box(
|
||||
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)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,15 @@ import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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.SelectableLazyItemScope
|
||||
import org.jetbrains.jewel.foundation.utils.Log
|
||||
@@ -99,7 +108,7 @@ fun <T> BasicLazyTree(
|
||||
state = treeState.delegate,
|
||||
keyActions = keyActions,
|
||||
interactionSource = interactionSource,
|
||||
pointerHandlingScopedActions = pointerEventScopedActions
|
||||
pointerHandlingScopedActions = pointerEventScopedActions,
|
||||
) {
|
||||
items(
|
||||
count = flattenedTree.size,
|
||||
@@ -107,13 +116,13 @@ fun <T> BasicLazyTree(
|
||||
val idPath = flattenedTree[it].idPath()
|
||||
idPath
|
||||
},
|
||||
contentType = { flattenedTree[it].data }
|
||||
contentType = { flattenedTree[it].data },
|
||||
) { itemIndex ->
|
||||
val element = flattenedTree[itemIndex]
|
||||
val elementState = TreeElementState.of(
|
||||
focused = isFocused,
|
||||
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)) }
|
||||
@@ -126,32 +135,32 @@ fun <T> BasicLazyTree(
|
||||
elementBackgroundSelectedFocused,
|
||||
elementBackgroundFocused,
|
||||
elementBackgroundSelected,
|
||||
backgroundShape
|
||||
backgroundShape,
|
||||
)
|
||||
.padding(elementContentPadding)
|
||||
.padding(start = (element.depth * indentSize.value).dp)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null
|
||||
indication = null,
|
||||
) {
|
||||
(pointerEventScopedActions as? DefaultTreeViewPointerEventAction)?.notifyItemClicked(
|
||||
item = flattenedTree[itemIndex] as Tree.Element<T>,
|
||||
scope = scope,
|
||||
doubleClickTimeDelayMillis = platformDoubleClickDelay.inWholeMilliseconds,
|
||||
onElementClick = onElementClick,
|
||||
onElementDoubleClick = onElementDoubleClick
|
||||
onElementDoubleClick = onElementDoubleClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
) {
|
||||
if (element is Tree.Element.Node) {
|
||||
Box(
|
||||
modifier = Modifier.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null
|
||||
indication = null,
|
||||
) {
|
||||
treeState.toggleNode(element.idPath())
|
||||
onElementDoubleClick(element as Tree.Element<T>)
|
||||
}
|
||||
},
|
||||
) {
|
||||
chevronContent(elementState)
|
||||
}
|
||||
@@ -177,54 +186,90 @@ private fun Modifier.elementBackground(
|
||||
state.isSelected && !state.isFocused -> selected
|
||||
else -> Color.Unspecified
|
||||
},
|
||||
shape = backgroundShape
|
||||
shape = backgroundShape,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
@JvmInline
|
||||
value class TreeElementState(val state: ULong) {
|
||||
value class TreeElementState(val state: ULong) : InteractiveComponentState, SelectableComponentState {
|
||||
|
||||
@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
|
||||
|
||||
@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
|
||||
|
||||
@Stable
|
||||
val isExpanded: Boolean
|
||||
get() = state and Expanded != 0UL
|
||||
|
||||
fun copy(
|
||||
focused: Boolean = isFocused,
|
||||
selected: Boolean = isSelected,
|
||||
expanded: Boolean = isExpanded,
|
||||
) = of(focused, selected, expanded)
|
||||
override fun toString(): String =
|
||||
"${javaClass.simpleName}(enabled=$isEnabled, focused=$isFocused, expanded=$isExpanded, " +
|
||||
"pressed=$isPressed, hovered=$isHovered, active=$isActive, selected=$isSelected)"
|
||||
|
||||
override fun toString() =
|
||||
"${javaClass.simpleName}(isFocused=$isFocused, isSelected=$isSelected, isExpanded=$isExpanded)"
|
||||
fun copy(
|
||||
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 {
|
||||
|
||||
private val Focused = 1UL shl 0
|
||||
private val Hovered = 1UL shl 1
|
||||
private val Selected = 1UL shl 2
|
||||
private val Expanded = 1UL shl 3
|
||||
private const val EXPANDED_BIT_OFFSET = CommonStateBitMask.FIRST_AVAILABLE_OFFSET
|
||||
|
||||
private val Expanded = 1UL shl EXPANDED_BIT_OFFSET
|
||||
|
||||
fun of(
|
||||
focused: Boolean,
|
||||
selected: Boolean,
|
||||
expanded: Boolean,
|
||||
enabled: Boolean = true,
|
||||
focused: Boolean = false,
|
||||
expanded: Boolean = false,
|
||||
hovered: Boolean = false,
|
||||
pressed: Boolean = false,
|
||||
active: Boolean = false,
|
||||
selected: Boolean = false,
|
||||
) = 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 (expanded) Expanded else 0UL)
|
||||
(if (active) Active else 0UL),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun flattenTree(
|
||||
private fun flattenTree(
|
||||
element: Tree.Element<*>,
|
||||
openNodes: SnapshotStateList<Any>,
|
||||
allNodes: SnapshotStateList<Any>,
|
||||
@@ -241,7 +286,7 @@ private suspend fun flattenTree(
|
||||
openNodes.removeAll(
|
||||
buildList {
|
||||
getAllSubNodes(element)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class TreeBuilder<T> : TreeGeneratorScope<T> {
|
||||
parent = null,
|
||||
previous = previous,
|
||||
next = null,
|
||||
id = elementBuilder.id
|
||||
id = elementBuilder.id,
|
||||
)
|
||||
|
||||
is Element.Node -> Tree.Element.Node(
|
||||
@@ -60,7 +60,7 @@ class TreeBuilder<T> : TreeGeneratorScope<T> {
|
||||
childrenGenerator = { parent -> generateElements(parent, elementBuilder) },
|
||||
previous = previous,
|
||||
next = null,
|
||||
id = elementBuilder.id
|
||||
id = elementBuilder.id,
|
||||
)
|
||||
}
|
||||
elements.add(current)
|
||||
@@ -87,7 +87,7 @@ private fun <T> generateElements(
|
||||
parent = parent,
|
||||
previous = previous,
|
||||
next = null,
|
||||
id = elementBuilder.id
|
||||
id = elementBuilder.id,
|
||||
)
|
||||
|
||||
is TreeBuilder.Element.Node -> Tree.Element.Node(
|
||||
@@ -98,7 +98,7 @@ private fun <T> generateElements(
|
||||
childrenGenerator = { generateElements(it, elementBuilder) },
|
||||
previous = previous,
|
||||
next = null,
|
||||
id = elementBuilder.id
|
||||
id = elementBuilder.id,
|
||||
)
|
||||
}
|
||||
previous.next = current
|
||||
@@ -116,6 +116,7 @@ private fun <T> evaluatePrevious(element: Tree.Element<T>): Tree.Element<T> = wh
|
||||
}
|
||||
|
||||
interface TreeGeneratorScope<T> {
|
||||
|
||||
fun addNode(
|
||||
data: T,
|
||||
id: Any = data.hashCode(),
|
||||
@@ -56,9 +56,9 @@ open class DefaultTreeViewOnKeyEvent(
|
||||
treeState.addElementsToSelection(
|
||||
listOf(
|
||||
currentIndex,
|
||||
prevIndex
|
||||
prevIndex,
|
||||
),
|
||||
null
|
||||
null,
|
||||
)
|
||||
treeState.lastKeyEventUsedMouse = true
|
||||
}
|
||||
@@ -88,9 +88,9 @@ open class DefaultTreeViewOnKeyEvent(
|
||||
treeState.addElementsToSelection(
|
||||
listOf(
|
||||
currentIndex,
|
||||
nextFlattenIndex
|
||||
nextFlattenIndex,
|
||||
),
|
||||
null
|
||||
null,
|
||||
)
|
||||
treeState.lastKeyEventUsedMouse = true
|
||||
}
|
||||
@@ -13,8 +13,8 @@ fun rememberTreeState(selectionMode: SelectionMode = SelectionMode.Single) = rem
|
||||
TreeState(
|
||||
SelectableLazyListState(
|
||||
LazyListState(),
|
||||
selectionMode
|
||||
)
|
||||
selectionMode,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ interface ButtonColors {
|
||||
focused = backgroundFocused,
|
||||
pressed = backgroundPressed,
|
||||
hovered = backgroundHovered,
|
||||
active = background
|
||||
)
|
||||
active = background,
|
||||
),
|
||||
)
|
||||
|
||||
val content: Color
|
||||
@@ -55,15 +55,15 @@ interface ButtonColors {
|
||||
focused = contentFocused,
|
||||
pressed = contentPressed,
|
||||
hovered = contentHovered,
|
||||
active = content
|
||||
)
|
||||
active = content,
|
||||
),
|
||||
)
|
||||
|
||||
val border: Color
|
||||
val borderDisabled: Color
|
||||
val borderFocused: Color
|
||||
val borderPressed: Color
|
||||
val borderHovered: Color
|
||||
val border: Brush
|
||||
val borderDisabled: Brush
|
||||
val borderFocused: Brush
|
||||
val borderPressed: Brush
|
||||
val borderHovered: Brush
|
||||
|
||||
@Composable
|
||||
fun borderFor(state: ButtonState) = rememberUpdatedState(
|
||||
@@ -73,8 +73,8 @@ interface ButtonColors {
|
||||
focused = borderFocused,
|
||||
pressed = borderPressed,
|
||||
hovered = borderHovered,
|
||||
active = border
|
||||
)
|
||||
active = border,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ interface CheckboxColors {
|
||||
!state.isEnabled -> checkboxBackgroundDisabled
|
||||
state.toggleableState == ToggleableState.On -> checkboxBackgroundSelected
|
||||
else -> checkboxBackground
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val content: Color
|
||||
@@ -46,20 +46,7 @@ interface CheckboxColors {
|
||||
!state.isEnabled -> contentDisabled
|
||||
state.toggleableState == ToggleableState.On -> contentSelected
|
||||
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.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import org.jetbrains.jewel.ChipState
|
||||
import org.jetbrains.jewel.IntelliJTheme
|
||||
|
||||
@Stable
|
||||
interface ChipStyle {
|
||||
@@ -28,17 +28,31 @@ interface ChipColors {
|
||||
val backgroundFocused: Brush
|
||||
val backgroundPressed: Brush
|
||||
val backgroundHovered: Brush
|
||||
val backgroundSelected: Brush
|
||||
val backgroundSelectedDisabled: Brush
|
||||
val backgroundSelectedPressed: Brush
|
||||
val backgroundSelectedFocused: Brush
|
||||
val backgroundSelectedHovered: Brush
|
||||
|
||||
@Composable
|
||||
fun backgroundFor(state: ChipState) = rememberUpdatedState(
|
||||
state.chooseValue(
|
||||
normal = background,
|
||||
disabled = backgroundDisabled,
|
||||
focused = backgroundFocused,
|
||||
pressed = backgroundPressed,
|
||||
hovered = backgroundHovered,
|
||||
active = background
|
||||
)
|
||||
if (state.isSelected) {
|
||||
when {
|
||||
!state.isEnabled -> backgroundSelectedDisabled
|
||||
state.isPressed -> backgroundSelectedPressed
|
||||
state.isFocused -> backgroundSelectedFocused
|
||||
state.isHovered -> backgroundSelectedHovered
|
||||
else -> backgroundSelected
|
||||
}
|
||||
} else {
|
||||
when {
|
||||
!state.isEnabled -> backgroundDisabled
|
||||
state.isPressed -> backgroundPressed
|
||||
state.isFocused -> backgroundFocused
|
||||
state.isHovered -> backgroundHovered
|
||||
else -> background
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val content: Color
|
||||
@@ -46,17 +60,31 @@ interface ChipColors {
|
||||
val contentFocused: Color
|
||||
val contentPressed: Color
|
||||
val contentHovered: Color
|
||||
val contentSelected: Color
|
||||
val contentSelectedDisabled: Color
|
||||
val contentSelectedPressed: Color
|
||||
val contentSelectedFocused: Color
|
||||
val contentSelectedHovered: Color
|
||||
|
||||
@Composable
|
||||
fun contentFor(state: ChipState) = rememberUpdatedState(
|
||||
state.chooseValue(
|
||||
normal = content,
|
||||
disabled = contentDisabled,
|
||||
focused = contentFocused,
|
||||
pressed = contentPressed,
|
||||
hovered = contentHovered,
|
||||
active = content
|
||||
)
|
||||
if (state.isSelected) {
|
||||
when {
|
||||
!state.isEnabled -> contentSelectedDisabled
|
||||
state.isPressed -> contentSelectedPressed
|
||||
state.isFocused -> contentSelectedFocused
|
||||
state.isHovered -> contentSelectedHovered
|
||||
else -> contentSelected
|
||||
}
|
||||
} else {
|
||||
when {
|
||||
!state.isEnabled -> contentDisabled
|
||||
state.isPressed -> contentPressed
|
||||
state.isFocused -> contentFocused
|
||||
state.isHovered -> contentHovered
|
||||
else -> content
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val border: Color
|
||||
@@ -64,27 +92,41 @@ interface ChipColors {
|
||||
val borderFocused: Color
|
||||
val borderPressed: Color
|
||||
val borderHovered: Color
|
||||
val borderSelected: Color
|
||||
val borderSelectedDisabled: Color
|
||||
val borderSelectedPressed: Color
|
||||
val borderSelectedFocused: Color
|
||||
val borderSelectedHovered: Color
|
||||
|
||||
@Composable
|
||||
fun borderFor(state: ChipState) = rememberUpdatedState(
|
||||
state.chooseValue(
|
||||
normal = border,
|
||||
disabled = borderDisabled,
|
||||
focused = borderFocused,
|
||||
pressed = borderPressed,
|
||||
hovered = borderHovered,
|
||||
active = border
|
||||
)
|
||||
if (state.isSelected) {
|
||||
when {
|
||||
!state.isEnabled -> borderSelectedDisabled
|
||||
state.isPressed && !IntelliJTheme.isSwingCompatMode -> borderSelectedPressed
|
||||
state.isFocused -> borderSelectedFocused
|
||||
state.isHovered && !IntelliJTheme.isSwingCompatMode -> borderSelectedHovered
|
||||
else -> borderSelected
|
||||
}
|
||||
} else {
|
||||
when {
|
||||
!state.isEnabled -> borderDisabled
|
||||
state.isPressed && !IntelliJTheme.isSwingCompatMode -> borderPressed
|
||||
state.isFocused -> borderFocused
|
||||
state.isHovered && !IntelliJTheme.isSwingCompatMode -> borderHovered
|
||||
else -> border
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Stable
|
||||
interface ChipMetrics {
|
||||
|
||||
val minSize: DpSize
|
||||
val cornerSize: CornerSize
|
||||
val padding: PaddingValues
|
||||
val borderWidth: Dp
|
||||
val borderWidthSelected: Dp
|
||||
}
|
||||
|
||||
val LocalChipStyle = staticCompositionLocalOf<ChipStyle> {
|
||||
|
||||
@@ -31,21 +31,17 @@ interface DropdownColors {
|
||||
val backgroundFocused: Color
|
||||
val backgroundPressed: Color
|
||||
val backgroundHovered: Color
|
||||
val backgroundWarning: Color
|
||||
val backgroundError: Color
|
||||
|
||||
@Composable
|
||||
fun backgroundFor(state: DropdownState) = rememberUpdatedState(
|
||||
state.chooseValueWithOutline(
|
||||
state.chooseValue(
|
||||
normal = background,
|
||||
disabled = backgroundDisabled,
|
||||
focused = backgroundFocused,
|
||||
pressed = backgroundPressed,
|
||||
hovered = backgroundHovered,
|
||||
warning = backgroundWarning,
|
||||
error = backgroundError,
|
||||
active = background
|
||||
)
|
||||
active = background,
|
||||
),
|
||||
)
|
||||
|
||||
val content: Color
|
||||
@@ -53,21 +49,17 @@ interface DropdownColors {
|
||||
val contentFocused: Color
|
||||
val contentPressed: Color
|
||||
val contentHovered: Color
|
||||
val contentWarning: Color
|
||||
val contentError: Color
|
||||
|
||||
@Composable
|
||||
fun contentFor(state: DropdownState) = rememberUpdatedState(
|
||||
state.chooseValueWithOutline(
|
||||
state.chooseValue(
|
||||
normal = content,
|
||||
disabled = contentDisabled,
|
||||
focused = contentFocused,
|
||||
pressed = contentPressed,
|
||||
hovered = contentHovered,
|
||||
warning = contentWarning,
|
||||
error = contentError,
|
||||
active = content
|
||||
)
|
||||
active = content,
|
||||
),
|
||||
)
|
||||
|
||||
val border: Color
|
||||
@@ -75,21 +67,17 @@ interface DropdownColors {
|
||||
val borderFocused: Color
|
||||
val borderPressed: Color
|
||||
val borderHovered: Color
|
||||
val borderWarning: Color
|
||||
val borderError: Color
|
||||
|
||||
@Composable
|
||||
fun borderFor(state: DropdownState) = rememberUpdatedState(
|
||||
state.chooseValueWithOutline(
|
||||
state.chooseValue(
|
||||
normal = border,
|
||||
disabled = borderDisabled,
|
||||
focused = borderFocused,
|
||||
pressed = borderPressed,
|
||||
hovered = borderHovered,
|
||||
warning = borderWarning,
|
||||
error = borderError,
|
||||
active = border
|
||||
)
|
||||
active = border,
|
||||
),
|
||||
)
|
||||
|
||||
val iconTint: Color
|
||||
@@ -97,27 +85,24 @@ interface DropdownColors {
|
||||
val iconTintFocused: Color
|
||||
val iconTintPressed: Color
|
||||
val iconTintHovered: Color
|
||||
val iconTintWarning: Color
|
||||
val iconTintError: Color
|
||||
|
||||
@Composable
|
||||
fun iconTintFor(state: DropdownState) = rememberUpdatedState(
|
||||
state.chooseValueWithOutline(
|
||||
state.chooseValue(
|
||||
normal = iconTint,
|
||||
disabled = iconTintDisabled,
|
||||
focused = iconTintFocused,
|
||||
pressed = iconTintPressed,
|
||||
hovered = iconTintHovered,
|
||||
warning = iconTintWarning,
|
||||
error = iconTintError,
|
||||
active = iconTint
|
||||
)
|
||||
active = iconTint,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Stable
|
||||
interface DropdownMetrics {
|
||||
|
||||
val arrowMinSize: DpSize
|
||||
val minSize: DpSize
|
||||
val cornerSize: CornerSize
|
||||
val contentPadding: PaddingValues
|
||||
|
||||
@@ -20,6 +20,7 @@ interface HorizontalProgressBarColors {
|
||||
|
||||
val track: Color
|
||||
val progress: Color
|
||||
val indeterminateBase: Color
|
||||
val indeterminateHighlight: Color
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.Dp
|
||||
@@ -29,21 +28,17 @@ interface InputFieldColors {
|
||||
val backgroundFocused: Color
|
||||
val backgroundPressed: Color
|
||||
val backgroundHovered: Color
|
||||
val backgroundWarning: Color
|
||||
val backgroundError: Color
|
||||
|
||||
@Composable
|
||||
fun backgroundFor(state: InputFieldState) = rememberUpdatedState(
|
||||
state.chooseValueWithOutline(
|
||||
state.chooseValue(
|
||||
normal = background,
|
||||
disabled = backgroundDisabled,
|
||||
focused = backgroundFocused,
|
||||
pressed = backgroundPressed,
|
||||
hovered = backgroundHovered,
|
||||
warning = backgroundWarning,
|
||||
error = backgroundError,
|
||||
active = background
|
||||
)
|
||||
active = background,
|
||||
),
|
||||
)
|
||||
|
||||
val content: Color
|
||||
@@ -51,21 +46,17 @@ interface InputFieldColors {
|
||||
val contentFocused: Color
|
||||
val contentPressed: Color
|
||||
val contentHovered: Color
|
||||
val contentWarning: Color
|
||||
val contentError: Color
|
||||
|
||||
@Composable
|
||||
fun contentFor(state: InputFieldState) = rememberUpdatedState(
|
||||
state.chooseValueWithOutline(
|
||||
state.chooseValue(
|
||||
normal = content,
|
||||
disabled = contentDisabled,
|
||||
focused = contentFocused,
|
||||
pressed = contentPressed,
|
||||
hovered = contentHovered,
|
||||
warning = contentWarning,
|
||||
error = contentError,
|
||||
active = content
|
||||
)
|
||||
active = content,
|
||||
),
|
||||
)
|
||||
|
||||
val border: Color
|
||||
@@ -73,43 +64,35 @@ interface InputFieldColors {
|
||||
val borderFocused: Color
|
||||
val borderPressed: Color
|
||||
val borderHovered: Color
|
||||
val borderWarning: Color
|
||||
val borderError: Color
|
||||
|
||||
@Composable
|
||||
fun borderFor(state: InputFieldState) = rememberUpdatedState(
|
||||
state.chooseValueWithOutline(
|
||||
state.chooseValue(
|
||||
normal = border,
|
||||
disabled = borderDisabled,
|
||||
focused = borderFocused,
|
||||
pressed = borderPressed,
|
||||
hovered = borderHovered,
|
||||
warning = borderWarning,
|
||||
error = borderError,
|
||||
active = border
|
||||
)
|
||||
active = border,
|
||||
),
|
||||
)
|
||||
|
||||
val cursor: Brush
|
||||
val cursorDisabled: Brush
|
||||
val cursorFocused: Brush
|
||||
val cursorPressed: Brush
|
||||
val cursorHovered: Brush
|
||||
val cursorWarning: Brush
|
||||
val cursorError: Brush
|
||||
val caret: Color
|
||||
val caretDisabled: Color
|
||||
val caretFocused: Color
|
||||
val caretPressed: Color
|
||||
val caretHovered: Color
|
||||
|
||||
@Composable
|
||||
fun cursorFor(state: InputFieldState) = rememberUpdatedState(
|
||||
state.chooseValueWithOutline(
|
||||
normal = cursor,
|
||||
disabled = cursorDisabled,
|
||||
focused = cursorFocused,
|
||||
pressed = cursorPressed,
|
||||
hovered = cursorHovered,
|
||||
warning = cursorWarning,
|
||||
error = cursorError,
|
||||
active = cursor
|
||||
)
|
||||
fun caretFor(state: InputFieldState) = rememberUpdatedState(
|
||||
state.chooseValue(
|
||||
normal = caret,
|
||||
disabled = caretDisabled,
|
||||
focused = caretFocused,
|
||||
pressed = caretPressed,
|
||||
hovered = caretHovered,
|
||||
active = caret,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,8 @@ import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.ResourceLoader
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import org.jetbrains.jewel.foundation.tree.TreeElementState
|
||||
import org.jetbrains.jewel.painterResource
|
||||
|
||||
@Stable
|
||||
interface LazyTreeStyle {
|
||||
@@ -33,11 +31,6 @@ interface LazyTreeColors {
|
||||
val contentSelected: Color
|
||||
val contentSelectedFocused: Color
|
||||
|
||||
val chevronTint: Color
|
||||
val chevronTintSelected: Color
|
||||
val chevronTintFocused: Color
|
||||
val chevronTintSelectedFocused: Color
|
||||
|
||||
@Composable
|
||||
fun contentFor(state: TreeElementState) = rememberUpdatedState(
|
||||
when {
|
||||
@@ -45,17 +38,7 @@ interface LazyTreeColors {
|
||||
state.isFocused -> contentFocused
|
||||
state.isSelected -> contentSelected
|
||||
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
|
||||
interface LazyTreeIcons {
|
||||
|
||||
val nodeChevron: String
|
||||
val nodeChevronCollapsed: StatefulPainterProvider<TreeElementState>
|
||||
val nodeChevronExpanded: StatefulPainterProvider<TreeElementState>
|
||||
|
||||
@Composable
|
||||
fun nodeChevronPainter(resourceLoader: ResourceLoader) = painterResource(nodeChevron, resourceLoader)
|
||||
fun nodeChevron(isExpanded: Boolean) =
|
||||
if (isExpanded) nodeChevronExpanded else nodeChevronCollapsed
|
||||
}
|
||||
|
||||
val LocalLazyTreeStyle = staticCompositionLocalOf<LazyTreeStyle> {
|
||||
|
||||
@@ -39,12 +39,9 @@ interface LinkColors {
|
||||
pressed = contentPressed,
|
||||
hovered = contentHovered,
|
||||
visited = contentVisited,
|
||||
active = content
|
||||
)
|
||||
active = content,
|
||||
),
|
||||
)
|
||||
|
||||
val iconTint: Color
|
||||
val iconTintDisabled: Color
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@@ -81,8 +78,8 @@ interface LinkTextStyles {
|
||||
pressed = pressed,
|
||||
hovered = hovered,
|
||||
visited = visited,
|
||||
active = normal
|
||||
)
|
||||
active = normal,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
@@ -24,7 +23,7 @@ interface MenuStyle {
|
||||
@Immutable
|
||||
interface MenuColors {
|
||||
|
||||
val background: Brush
|
||||
val background: Color
|
||||
val border: Color
|
||||
val shadow: Color
|
||||
val itemColors: MenuItemColors
|
||||
@@ -34,8 +33,7 @@ interface MenuColors {
|
||||
interface MenuMetrics {
|
||||
|
||||
val cornerSize: CornerSize
|
||||
val margin: PaddingValues
|
||||
val padding: PaddingValues
|
||||
val menuMargin: PaddingValues
|
||||
val contentPadding: PaddingValues
|
||||
val offset: DpOffset
|
||||
val shadowSize: Dp
|
||||
@@ -47,17 +45,17 @@ interface MenuMetrics {
|
||||
@Stable
|
||||
interface MenuItemMetrics {
|
||||
|
||||
val cornerSize: CornerSize
|
||||
val padding: PaddingValues
|
||||
val selectionCornerSize: CornerSize
|
||||
val outerPadding: PaddingValues
|
||||
val contentPadding: PaddingValues
|
||||
val separatorPadding: PaddingValues
|
||||
val separatorThickness: Dp
|
||||
}
|
||||
|
||||
@Stable
|
||||
interface SubmenuMetrics {
|
||||
|
||||
val offset: DpOffset
|
||||
val itemPadding: PaddingValues
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@@ -77,8 +75,8 @@ interface MenuItemColors {
|
||||
active = background,
|
||||
focused = backgroundFocused,
|
||||
pressed = backgroundPressed,
|
||||
hovered = backgroundHovered
|
||||
)
|
||||
hovered = backgroundHovered,
|
||||
),
|
||||
)
|
||||
|
||||
val content: Color
|
||||
@@ -95,8 +93,8 @@ interface MenuItemColors {
|
||||
focused = contentFocused,
|
||||
pressed = contentPressed,
|
||||
hovered = contentHovered,
|
||||
active = content
|
||||
)
|
||||
active = content,
|
||||
),
|
||||
)
|
||||
|
||||
val iconTint: Color
|
||||
@@ -113,8 +111,8 @@ interface MenuItemColors {
|
||||
focused = iconTintFocused,
|
||||
pressed = iconTintPressed,
|
||||
hovered = iconTintHovered,
|
||||
active = iconTint
|
||||
)
|
||||
active = iconTint,
|
||||
),
|
||||
)
|
||||
|
||||
val separator: Color
|
||||
@@ -122,7 +120,8 @@ interface MenuItemColors {
|
||||
|
||||
@Immutable
|
||||
interface MenuIcons {
|
||||
val submenuChevron: String
|
||||
|
||||
val submenuChevron: StatefulPainterProvider<MenuItemState>
|
||||
}
|
||||
|
||||
val LocalMenuStyle = staticCompositionLocalOf<MenuStyle> {
|
||||
|
||||
@@ -36,65 +36,7 @@ interface RadioButtonColors {
|
||||
state.isSelected -> contentSelected
|
||||
state.isHovered -> contentHovered
|
||||
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
|
||||
|
||||
@Immutable
|
||||
class ResourcePainterProvider<T : InteractiveComponentState>(
|
||||
open class ResourcePainterProvider<T : InteractiveComponentState>(
|
||||
private val basePath: String,
|
||||
private val svgLoader: SvgLoader,
|
||||
private val prefixTokensProvider: (state: T) -> String = { "" },
|
||||
@@ -26,9 +26,9 @@ class ResourcePainterProvider<T : InteractiveComponentState>(
|
||||
override fun getPainter(state: T, resourceLoader: ResourceLoader): State<Painter> {
|
||||
val isSvg = basePath.endsWith(".svg", ignoreCase = true)
|
||||
val painter = if (isSvg) {
|
||||
svgLoader.loadSvgResource(basePath, resourceLoader) { patchPath(state, basePath) }
|
||||
svgLoader.loadSvgResource(basePath, resourceLoader) { patchPath(state, basePath, resourceLoader) }
|
||||
} else {
|
||||
val patchedPath = patchPath(state, basePath)
|
||||
val patchedPath = patchPath(state, basePath, resourceLoader)
|
||||
painterResource(patchedPath, resourceLoader)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class ResourcePainterProvider<T : InteractiveComponentState>(
|
||||
}
|
||||
|
||||
@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('/')
|
||||
append(basePath.substringBeforeLast('.').substringAfterLast('/'))
|
||||
|
||||
@@ -69,9 +69,9 @@ interface TabColors {
|
||||
focused = contentFocused,
|
||||
pressed = contentPressed,
|
||||
hovered = contentHovered,
|
||||
active = content
|
||||
active = content,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@Composable
|
||||
@@ -84,7 +84,7 @@ interface TabColors {
|
||||
state.isActive -> background
|
||||
state.isSelected -> backgroundSelected
|
||||
else -> background
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@Composable
|
||||
@@ -97,9 +97,9 @@ interface TabColors {
|
||||
focused = underlineFocused,
|
||||
pressed = underlinePressed,
|
||||
hovered = underlineHovered,
|
||||
active = underline
|
||||
active = underline,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -123,9 +123,9 @@ interface TabContentAlpha {
|
||||
focused = iconFocused,
|
||||
pressed = iconPressed,
|
||||
hovered = iconHovered,
|
||||
active = iconNormal
|
||||
active = iconNormal,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val labelNormal: Float
|
||||
@@ -145,9 +145,9 @@ interface TabContentAlpha {
|
||||
focused = labelFocused,
|
||||
pressed = labelPressed,
|
||||
hovered = labelHovered,
|
||||
active = labelNormal
|
||||
active = labelNormal,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
package org.jetbrains.jewel.styling
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import org.jetbrains.jewel.InputFieldState
|
||||
|
||||
@Stable
|
||||
interface TextAreaStyle : InputFieldStyle {
|
||||
|
||||
override val colors: TextAreaColors
|
||||
override val metrics: InputFieldMetrics
|
||||
val hintTextStyle: TextStyle
|
||||
}
|
||||
|
||||
@Immutable
|
||||
interface TextAreaColors : InputFieldColors {
|
||||
|
||||
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> {
|
||||
|
||||
@@ -55,7 +55,7 @@ object PaletteMapperFactory {
|
||||
"Checkbox.Focus.Thin.Selected" to "#ACCFF7",
|
||||
"Checkbox.Focus.Thin.Selected.Dark" to "#466D94",
|
||||
"Tree.iconColor" to "#808080",
|
||||
"Tree.iconColor.Dark" to "#AFB1B3"
|
||||
"Tree.iconColor.Dark" to "#AFB1B3",
|
||||
)
|
||||
|
||||
fun create(
|
||||
|
||||
@@ -104,7 +104,9 @@ style:
|
||||
includeLineWrapping: false
|
||||
ForbiddenComment:
|
||||
active: true
|
||||
values: [ 'STOPSHIP' ]
|
||||
comments:
|
||||
- value: 'STOPSHIP'
|
||||
reason: 'Forbidden STOPSHIP marker in comment, please address before shipping'
|
||||
allowedPatterns: ''
|
||||
LoopWithTooManyJumpStatements:
|
||||
active: true
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
[versions]
|
||||
composeDesktop = "1.5.0-dev1136"
|
||||
coroutines = "1.6.4"
|
||||
detekt = "1.22.0"
|
||||
composeDesktop = "1.5.0-rc02"
|
||||
coroutines = "1.7.3"
|
||||
detekt = "1.23.1"
|
||||
idea = "232.8660.185"
|
||||
ideaGradlePlugin = "1.13.0"
|
||||
ideaGradlePlugin = "1.15.0"
|
||||
javaSarif = "2.0"
|
||||
kotlinSarif = "0.4.0"
|
||||
jna = "5.10.0"
|
||||
kotlin = "1.8.20"
|
||||
kotlin = "1.8.21"
|
||||
dokka = "1.8.20"
|
||||
kotlinterGradlePlugin = "3.12.0"
|
||||
kotlinterGradlePlugin = "3.16.0"
|
||||
kotlinxSerialization = "1.5.1"
|
||||
kotlinpoet = "1.14.2"
|
||||
|
||||
[libraries]
|
||||
compose-components-splitpane = { module = "org.jetbrains.compose.components:components-splitpane", version.ref = "composeDesktop" }
|
||||
javaSarif = { module = "com.contrastsecurity:java-sarif", version.ref = "javaSarif" }
|
||||
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-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" }
|
||||
|
||||
@@ -2,52 +2,42 @@
|
||||
|
||||
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.openapi.application.Application
|
||||
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.Topic
|
||||
import com.intellij.util.ui.DirProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.ProducerScope
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.jetbrains.skiko.toSkikoTypeface
|
||||
import javax.swing.UIManager
|
||||
import java.awt.Color as AwtColor
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
|
||||
internal val IntelliJApplication: Application
|
||||
get() = ApplicationManager.getApplication()
|
||||
|
||||
internal val Application.intellijThemeFlow
|
||||
get() = lookAndFeelFlow.map { ObtainIntelliJTheme() }
|
||||
|
||||
internal val Application.lookAndFeelFlow: Flow<LafManager>
|
||||
get() = messageBusFlow(LafManagerListener.TOPIC, { LafManager.getInstance()!! }) {
|
||||
LafManagerListener { trySend(it) }
|
||||
private val Application.lookAndFeelFlow: Flow<Unit>
|
||||
get() = messageBus.flow(LafManagerListener.TOPIC) {
|
||||
LafManagerListener { trySend(Unit) }
|
||||
}
|
||||
|
||||
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>,
|
||||
initialValue: (suspend () -> K)? = null,
|
||||
@BuilderInference listener: ProducerScope<K>.() -> L
|
||||
listener: ProducerScope<K>.() -> L,
|
||||
): Flow<K> = callbackFlow {
|
||||
initialValue?.let { send(it()) }
|
||||
val connection: SimpleMessageBusConnection = messageBus.simpleConnect()
|
||||
val connection: SimpleMessageBusConnection = simpleConnect()
|
||||
connection.subscribe(topic, listener())
|
||||
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)
|
||||
@Target(
|
||||
AnnotationTarget.PROPERTY,
|
||||
AnnotationTarget.PROPERTY_GETTER,
|
||||
AnnotationTarget.VALUE_PARAMETER,
|
||||
AnnotationTarget.EXPRESSION
|
||||
AnnotationTarget.EXPRESSION,
|
||||
)
|
||||
annotation class SwingLafKey(
|
||||
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