Добавлены тесты рендера и стилизации Markdown, реализован MarkdownStyle для самостоятельного управления стилями.
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
package pw.binom.agentik.desktop.ui
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
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 pw.binom.agentik.desktop.ui.theme.AgentikColors
|
||||
import pw.binom.agentik.desktop.ui.theme.darkAgentikColors
|
||||
|
||||
/**
|
||||
* Стиль отрисовки markdown: цвета, шрифты и отступы, из которых рендер
|
||||
* ([renderMarkdownSegments]) берёт решительно всё.
|
||||
*
|
||||
* Зачем отдельный контракт: рендер не должен знать ни про тему приложения, ни
|
||||
* про конкретные числа. Раньше он получал только `textColor` + `fontSize`, а
|
||||
* остальное (фон код-блока, цвет ссылки, отступы, фон таблицы) тянул из
|
||||
* `AgentikTheme`/`MaterialTheme` напрямую — из-за чего часть слотов жила вне
|
||||
* темы, а тело сообщения могло уехать в цвет по умолчанию (чёрный).
|
||||
*
|
||||
* Реализация под наш клиент — [AgentikMarkdownStyle]; текущий стиль лежит в
|
||||
* [LocalMarkdownStyle] (его подкладывает `AgentikTheme`), поэтому вызову
|
||||
* достаточно `renderMarkdownSegments(content)`.
|
||||
*/
|
||||
@Immutable
|
||||
interface MarkdownStyle {
|
||||
/** Цвет основного текста сообщения (в тёмной теме — светлый). */
|
||||
val textColor: Color
|
||||
|
||||
/** Кегль тела сообщения. */
|
||||
val bodyFontSize: TextUnit
|
||||
val bodyFontFamily: FontFamily
|
||||
|
||||
/** Интервал строк тела; [TextUnit.Unspecified] — на усмотрение движка. */
|
||||
val bodyLineHeight: TextUnit
|
||||
|
||||
/** Ссылки: цвет + подчёркивание. */
|
||||
val linkColor: Color
|
||||
|
||||
/** Inline-код: `code`. */
|
||||
val monospaceFamily: FontFamily
|
||||
val inlineCodeFontSize: TextUnit
|
||||
val inlineCodeBackground: Color
|
||||
|
||||
/** Блок кода (``` … ```). */
|
||||
val codeBackground: Color
|
||||
val codeTextColor: Color
|
||||
val codeHeaderBackground: Color
|
||||
val codeFontSize: TextUnit
|
||||
|
||||
/** Таблица. */
|
||||
val tableSurface: Color
|
||||
val tableHeaderBackground: Color
|
||||
val tableStripeBackground: Color
|
||||
val tableFontSize: TextUnit
|
||||
|
||||
/** Мелкие подписи (язык код-блока, иконка копирования) и разделитель `---`. */
|
||||
val labelColor: Color
|
||||
val labelFontSize: TextUnit
|
||||
val divider: Color
|
||||
|
||||
/** Отступы. */
|
||||
val paragraphSpacing: Dp
|
||||
val blockSpacing: Dp
|
||||
val tableSpacing: Dp
|
||||
val codePadding: Dp
|
||||
val codeHeaderPaddingHorizontal: Dp
|
||||
val codeHeaderPaddingVertical: Dp
|
||||
val tableCellPaddingHorizontal: Dp
|
||||
val tableCellPaddingVertical: Dp
|
||||
val copyIconSize: Dp
|
||||
|
||||
/** Ширина контента, когда сообщение лежит в горизонтально скроллящемся пузыре. */
|
||||
val maxContentWidth: Dp
|
||||
}
|
||||
|
||||
/**
|
||||
* База, накладываемая на ВЕСЬ текстовый сегмент.
|
||||
*
|
||||
* Парсер заводит стилевые спаны только для размеченных участков (жирный/код/
|
||||
* ссылка), поэтому без этой базы «простой» текст остаётся без цвета и рисуется
|
||||
* цветом по умолчанию (чёрным) — это и был баг «текст чёрный вместо белого».
|
||||
*
|
||||
* Именно функция, а не свойство интерфейса: с делегированием
|
||||
* (`object : MarkdownStyle by otherStyle`) свойство с реализацией по умолчанию
|
||||
* уезжает в делегат и перекрытый `textColor` в базе не учитывается.
|
||||
* [bodyLineHeight] сюда не входит — интервал строк задаётся у абзаца
|
||||
* (`Text(lineHeight = …)`), в `SpanStyle` такого слота нет.
|
||||
*/
|
||||
fun MarkdownStyle.bodySpan(): SpanStyle = SpanStyle(
|
||||
color = textColor,
|
||||
fontSize = bodyFontSize,
|
||||
fontFamily = bodyFontFamily,
|
||||
)
|
||||
|
||||
/**
|
||||
* Наш стиль: цвета — из [AgentikColors] активной темы, типографика и отступы —
|
||||
* из макетов клиента (`sketches/index.html`).
|
||||
*/
|
||||
@Immutable
|
||||
class AgentikMarkdownStyle(private val colors: AgentikColors) : MarkdownStyle {
|
||||
override val textColor: Color get() = colors.text
|
||||
|
||||
override val bodyFontSize: TextUnit = 13.5.sp
|
||||
override val bodyFontFamily: FontFamily = FontFamily.Default
|
||||
override val bodyLineHeight: TextUnit = 20.sp
|
||||
|
||||
override val linkColor: Color get() = colors.accent
|
||||
|
||||
override val monospaceFamily: FontFamily = FontFamily.Monospace
|
||||
override val inlineCodeFontSize: TextUnit = 13.sp
|
||||
override val inlineCodeBackground: Color get() = colors.panelElevated
|
||||
|
||||
override val codeBackground: Color get() = colors.codeBackground
|
||||
override val codeTextColor: Color get() = colors.codeText
|
||||
override val codeHeaderBackground: Color get() = colors.panelElevated
|
||||
override val codeFontSize: TextUnit = 13.sp
|
||||
|
||||
override val tableSurface: Color get() = colors.panel.copy(alpha = 0.3f)
|
||||
override val tableHeaderBackground: Color get() = colors.accent.copy(alpha = 0.1f)
|
||||
override val tableStripeBackground: Color get() = colors.text.copy(alpha = 0.03f)
|
||||
override val tableFontSize: TextUnit = 13.5.sp
|
||||
|
||||
override val labelColor: Color get() = colors.textDim
|
||||
override val labelFontSize: TextUnit = 12.sp
|
||||
override val divider: Color get() = colors.text.copy(alpha = 0.25f)
|
||||
|
||||
override val paragraphSpacing: Dp = 12.dp
|
||||
override val blockSpacing: Dp = 4.dp
|
||||
override val tableSpacing: Dp = 6.dp
|
||||
override val codePadding: Dp = 12.dp
|
||||
override val codeHeaderPaddingHorizontal: Dp = 12.dp
|
||||
override val codeHeaderPaddingVertical: Dp = 6.dp
|
||||
override val tableCellPaddingHorizontal: Dp = 8.dp
|
||||
override val tableCellPaddingVertical: Dp = 6.dp
|
||||
override val copyIconSize: Dp = 16.dp
|
||||
|
||||
override val maxContentWidth: Dp = 600.dp
|
||||
}
|
||||
|
||||
/** Стиль по умолчанию — тёмная тема (эталонная); совпадает с дефолтом [AgentikColors]. */
|
||||
private val DefaultMarkdownStyle: MarkdownStyle = AgentikMarkdownStyle(darkAgentikColors)
|
||||
|
||||
/**
|
||||
* Текущий стиль markdown. Подкладывается `AgentikTheme`, поэтому экраны читают
|
||||
* его неявно: `renderMarkdownSegments(content)`.
|
||||
*/
|
||||
val LocalMarkdownStyle = staticCompositionLocalOf<MarkdownStyle> { DefaultMarkdownStyle }
|
||||
Reference in New Issue
Block a user