Merge pull request #1795 from MarcelP102/Select-Color-Redesign

[UX] Select Color redesign and feature parity

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-15 00:21:10 +03:00
commit 843cbcd607
56 files changed
+4716 -606

No files matched your search

Generated
+12
View File
@@ -2593,6 +2593,8 @@ dependencies = [
"iced_core",
"iced_fonts_macros",
"iced_widget",
"num-format",
"num-traits",
"web-time",
]
@@ -3868,6 +3870,16 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "num-format"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
dependencies = [
"arrayvec",
"itoa",
]
[[package]]
name = "num-integer"
version = "0.1.46"
+1 -1
View File
@@ -30,7 +30,7 @@ url = "2"
iced = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39", features = ["image", "svg", "advanced", "canvas", "smol", "markdown"] }
iced_runtime = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
iced_aw = { git = "https://github.com/HakanSeven12/iced_aw.git", branch = "agent/fix-iced-fonts", features = ["menu", "context_menu", "color_picker", "drop_down"] }
iced_aw = { git = "https://github.com/HakanSeven12/iced_aw.git", branch = "agent/fix-iced-fonts", features = ["menu", "context_menu", "drop_down", "number_input"] }
bytemuck = { version = "1.25", features = ["derive"] }
glam = { version = "0.33", features = ["bytemuck"] }
rfd = "0.17"
+12
View File
@@ -0,0 +1,12 @@
# Built-in colour books
AutoCAD `.acb` colour books built into the Select Color panel. Each file
carries its source and licence in its header comment.
| File | Book | Source and licence |
|---|---|---|
| `ral-classic.acb` | RAL CLASSIC, 216 approximate sRGB values | Adapted from Wikipedia, "List of RAL colours"; CC BY-SA 4.0 |
Values are approximate screen representations, not colour-binding
references. RAL is a registered trademark of RAL gGmbH; this file is not
affiliated with or endorsed by RAL gGmbH.
+301
View File
@@ -0,0 +1,301 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
RAL Classic: non-official approximate sRGB screen values, 216 colours.
Not colour-binding: use an official physical RAL reference for colour-critical work.
Colour values adapted from Wikipedia contributors, "List of RAL colours"
(https://en.wikipedia.org/wiki/List_of_RAL_colours), licensed CC BY-SA 4.0
(https://creativecommons.org/licenses/by-sa/4.0/). This file is shared under the same licence.
RAL is a registered trademark of RAL gGmbH; this file is not affiliated with,
endorsed by or licensed by RAL gGmbH.
-->
<colorBook>
<bookName>RAL CLASSIC</bookName>
<majorVersion>1</majorVersion>
<minorVersion>0</minorVersion>
<colorPage>
<colorEntry><colorName>RAL 1000</colorName><RGB8><red>205</red><green>186</green><blue>136</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1001</colorName><RGB8><red>208</red><green>176</green><blue>132</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1002</colorName><RGB8><red>210</red><green>170</green><blue>109</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1003</colorName><RGB8><red>249</red><green>168</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1004</colorName><RGB8><red>228</red><green>158</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1005</colorName><RGB8><red>203</red><green>142</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1006</colorName><RGB8><red>226</red><green>144</green><blue>0</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 1007</colorName><RGB8><red>232</red><green>140</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1011</colorName><RGB8><red>175</red><green>128</green><blue>79</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1012</colorName><RGB8><red>221</red><green>175</green><blue>39</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1013</colorName><RGB8><red>227</red><green>217</green><blue>198</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1014</colorName><RGB8><red>221</red><green>196</green><blue>154</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1015</colorName><RGB8><red>230</red><green>210</green><blue>181</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1016</colorName><RGB8><red>241</red><green>221</green><blue>56</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 1017</colorName><RGB8><red>246</red><green>169</green><blue>80</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1018</colorName><RGB8><red>250</red><green>202</green><blue>48</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1019</colorName><RGB8><red>164</red><green>143</green><blue>122</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1020</colorName><RGB8><red>160</red><green>143</green><blue>101</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1021</colorName><RGB8><red>246</red><green>182</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1023</colorName><RGB8><red>247</red><green>181</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1024</colorName><RGB8><red>186</red><green>143</green><blue>76</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 1026</colorName><RGB8><red>255</red><green>255</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1027</colorName><RGB8><red>167</red><green>127</green><blue>14</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1028</colorName><RGB8><red>255</red><green>155</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1032</colorName><RGB8><red>226</red><green>163</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1033</colorName><RGB8><red>249</red><green>154</green><blue>28</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1034</colorName><RGB8><red>235</red><green>156</green><blue>82</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1035</colorName><RGB8><red>144</red><green>131</green><blue>112</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 1036</colorName><RGB8><red>128</red><green>100</green><blue>63</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 1037</colorName><RGB8><red>240</red><green>146</green><blue>0</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 2000</colorName><RGB8><red>221</red><green>121</green><blue>7</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 2001</colorName><RGB8><red>190</red><green>78</green><blue>32</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 2002</colorName><RGB8><red>198</red><green>57</green><blue>39</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 2003</colorName><RGB8><red>250</red><green>132</green><blue>43</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 2004</colorName><RGB8><red>231</red><green>91</green><blue>18</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 2005</colorName><RGB8><red>255</red><green>35</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 2007</colorName><RGB8><red>255</red><green>164</green><blue>33</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 2008</colorName><RGB8><red>243</red><green>117</green><blue>44</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 2009</colorName><RGB8><red>225</red><green>85</green><blue>1</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 2010</colorName><RGB8><red>212</red><green>101</green><blue>47</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 2011</colorName><RGB8><red>236</red><green>124</green><blue>37</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 2012</colorName><RGB8><red>219</red><green>106</green><blue>80</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 2013</colorName><RGB8><red>149</red><green>69</green><blue>39</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 2017</colorName><RGB8><red>250</red><green>68</green><blue>2</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 3000</colorName><RGB8><red>171</red><green>37</green><blue>36</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3001</colorName><RGB8><red>160</red><green>33</green><blue>40</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3002</colorName><RGB8><red>161</red><green>35</green><blue>43</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3003</colorName><RGB8><red>141</red><green>29</green><blue>44</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3004</colorName><RGB8><red>112</red><green>31</green><blue>41</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3005</colorName><RGB8><red>94</red><green>32</green><blue>40</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3007</colorName><RGB8><red>64</red><green>34</green><blue>37</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 3009</colorName><RGB8><red>112</red><green>55</green><blue>49</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3011</colorName><RGB8><red>126</red><green>41</green><blue>44</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3012</colorName><RGB8><red>203</red><green>141</green><blue>115</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3013</colorName><RGB8><red>156</red><green>50</green><blue>46</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3014</colorName><RGB8><red>212</red><green>116</green><blue>121</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3015</colorName><RGB8><red>225</red><green>166</green><blue>173</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3016</colorName><RGB8><red>172</red><green>64</green><blue>52</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 3017</colorName><RGB8><red>211</red><green>84</green><blue>95</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3018</colorName><RGB8><red>209</red><green>65</green><blue>82</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3020</colorName><RGB8><red>193</red><green>18</green><blue>28</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3022</colorName><RGB8><red>213</red><green>109</green><blue>86</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3024</colorName><RGB8><red>247</red><green>0</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3026</colorName><RGB8><red>255</red><green>0</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3027</colorName><RGB8><red>180</red><green>32</green><blue>65</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 3028</colorName><RGB8><red>231</red><green>37</green><blue>18</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3031</colorName><RGB8><red>172</red><green>50</green><blue>59</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3032</colorName><RGB8><red>113</red><green>21</green><blue>33</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 3033</colorName><RGB8><red>178</red><green>76</green><blue>67</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 4001</colorName><RGB8><red>138</red><green>90</green><blue>131</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 4002</colorName><RGB8><red>147</red><green>61</green><blue>80</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 4003</colorName><RGB8><red>209</red><green>91</green><blue>143</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 4004</colorName><RGB8><red>105</red><green>22</green><blue>57</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 4005</colorName><RGB8><red>131</red><green>99</green><blue>157</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 4006</colorName><RGB8><red>153</red><green>37</green><blue>114</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 4007</colorName><RGB8><red>74</red><green>32</green><blue>59</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 4008</colorName><RGB8><red>144</red><green>70</green><blue>132</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 4009</colorName><RGB8><red>163</red><green>137</green><blue>149</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 4010</colorName><RGB8><red>198</red><green>54</green><blue>120</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 4011</colorName><RGB8><red>135</red><green>115</green><blue>161</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 4012</colorName><RGB8><red>107</red><green>104</green><blue>128</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 5000</colorName><RGB8><red>56</red><green>76</green><blue>112</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5001</colorName><RGB8><red>31</red><green>71</green><blue>100</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5002</colorName><RGB8><red>43</red><green>44</green><blue>124</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5003</colorName><RGB8><red>42</red><green>55</green><blue>86</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5004</colorName><RGB8><red>29</red><green>31</green><blue>42</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5005</colorName><RGB8><red>21</red><green>72</green><blue>137</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5007</colorName><RGB8><red>65</red><green>103</green><blue>141</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 5008</colorName><RGB8><red>49</red><green>60</green><blue>72</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5009</colorName><RGB8><red>46</red><green>89</green><blue>120</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5010</colorName><RGB8><red>19</red><green>68</green><blue>124</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5011</colorName><RGB8><red>35</red><green>44</green><blue>63</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5012</colorName><RGB8><red>52</red><green>129</green><blue>184</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5013</colorName><RGB8><red>35</red><green>45</green><blue>83</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5014</colorName><RGB8><red>108</red><green>124</green><blue>152</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 5015</colorName><RGB8><red>40</red><green>116</green><blue>178</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5017</colorName><RGB8><red>14</red><green>81</green><blue>141</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5018</colorName><RGB8><red>33</red><green>136</green><blue>143</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5019</colorName><RGB8><red>26</red><green>87</green><blue>132</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5020</colorName><RGB8><red>11</red><green>65</green><blue>81</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5021</colorName><RGB8><red>7</red><green>115</green><blue>122</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5022</colorName><RGB8><red>47</red><green>42</green><blue>90</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 5023</colorName><RGB8><red>77</red><green>102</green><blue>142</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5024</colorName><RGB8><red>106</red><green>147</green><blue>176</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5025</colorName><RGB8><red>41</red><green>100</green><blue>120</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 5026</colorName><RGB8><red>16</red><green>44</green><blue>84</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 6000</colorName><RGB8><red>50</red><green>118</green><blue>98</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6001</colorName><RGB8><red>40</red><green>113</green><blue>62</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6002</colorName><RGB8><red>39</red><green>98</green><blue>53</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6003</colorName><RGB8><red>75</red><green>87</green><blue>62</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6004</colorName><RGB8><red>14</red><green>66</green><blue>67</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6005</colorName><RGB8><red>15</red><green>67</green><blue>54</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6006</colorName><RGB8><red>64</red><green>67</green><blue>59</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 6007</colorName><RGB8><red>40</red><green>52</green><blue>36</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6008</colorName><RGB8><red>53</red><green>56</green><blue>46</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6009</colorName><RGB8><red>38</red><green>57</green><blue>47</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6010</colorName><RGB8><red>62</red><green>117</green><blue>59</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6011</colorName><RGB8><red>104</red><green>130</green><blue>91</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6012</colorName><RGB8><red>49</red><green>64</green><blue>61</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6013</colorName><RGB8><red>121</red><green>124</green><blue>90</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 6014</colorName><RGB8><red>68</red><green>67</green><blue>55</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6015</colorName><RGB8><red>61</red><green>64</green><blue>58</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6016</colorName><RGB8><red>2</red><green>106</green><blue>82</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6017</colorName><RGB8><red>70</red><green>134</green><blue>65</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6018</colorName><RGB8><red>72</red><green>164</green><blue>63</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6019</colorName><RGB8><red>183</red><green>217</green><blue>177</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6020</colorName><RGB8><red>53</red><green>71</green><blue>51</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 6021</colorName><RGB8><red>134</red><green>164</green><blue>124</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6022</colorName><RGB8><red>62</red><green>60</green><blue>50</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6024</colorName><RGB8><red>0</red><green>135</green><blue>84</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6025</colorName><RGB8><red>83</red><green>117</green><blue>60</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6026</colorName><RGB8><red>0</red><green>93</green><blue>82</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6027</colorName><RGB8><red>129</red><green>192</green><blue>187</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6028</colorName><RGB8><red>45</red><green>85</green><blue>70</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 6029</colorName><RGB8><red>0</red><green>114</green><blue>67</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6032</colorName><RGB8><red>15</red><green>133</green><blue>88</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6033</colorName><RGB8><red>71</red><green>138</green><blue>132</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6034</colorName><RGB8><red>127</red><green>176</green><blue>178</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6035</colorName><RGB8><red>27</red><green>84</green><blue>44</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6036</colorName><RGB8><red>0</red><green>93</green><blue>76</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6037</colorName><RGB8><red>37</red><green>231</green><blue>18</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 6038</colorName><RGB8><red>0</red><green>247</green><blue>0</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 6039</colorName><RGB8><red>179</red><green>196</green><blue>62</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 7000</colorName><RGB8><red>126</red><green>139</green><blue>146</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7001</colorName><RGB8><red>143</red><green>153</green><blue>159</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7002</colorName><RGB8><red>129</red><green>127</green><blue>104</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7003</colorName><RGB8><red>122</red><green>123</green><blue>109</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7004</colorName><RGB8><red>158</red><green>160</green><blue>161</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7005</colorName><RGB8><red>107</red><green>113</green><blue>111</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7006</colorName><RGB8><red>117</red><green>111</green><blue>97</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 7008</colorName><RGB8><red>116</red><green>102</green><blue>67</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7009</colorName><RGB8><red>91</red><green>98</green><blue>89</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7010</colorName><RGB8><red>87</red><green>93</green><blue>87</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7011</colorName><RGB8><red>85</red><green>93</green><blue>97</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7012</colorName><RGB8><red>89</red><green>97</green><blue>99</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7013</colorName><RGB8><red>85</red><green>85</green><blue>72</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7015</colorName><RGB8><red>81</red><green>86</green><blue>92</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 7016</colorName><RGB8><red>55</red><green>63</green><blue>67</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7021</colorName><RGB8><red>46</red><green>50</green><blue>52</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7022</colorName><RGB8><red>75</red><green>77</green><blue>70</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7023</colorName><RGB8><red>129</red><green>132</green><blue>121</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7024</colorName><RGB8><red>71</red><green>74</green><blue>80</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7026</colorName><RGB8><red>55</red><green>68</green><blue>71</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7030</colorName><RGB8><red>147</red><green>147</green><blue>136</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 7031</colorName><RGB8><red>93</red><green>105</green><blue>112</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7032</colorName><RGB8><red>185</red><green>185</green><blue>168</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7033</colorName><RGB8><red>129</red><green>137</green><blue>121</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7034</colorName><RGB8><red>147</red><green>145</green><blue>118</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7035</colorName><RGB8><red>203</red><green>208</green><blue>204</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7036</colorName><RGB8><red>154</red><green>150</green><blue>151</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7037</colorName><RGB8><red>124</red><green>127</green><blue>126</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 7038</colorName><RGB8><red>180</red><green>184</green><blue>176</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7039</colorName><RGB8><red>107</red><green>105</green><blue>95</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7040</colorName><RGB8><red>157</red><green>163</green><blue>166</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7042</colorName><RGB8><red>143</red><green>150</green><blue>149</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7043</colorName><RGB8><red>78</red><green>84</green><blue>81</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7044</colorName><RGB8><red>189</red><green>189</green><blue>178</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7045</colorName><RGB8><red>145</red><green>150</green><blue>154</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 7046</colorName><RGB8><red>130</red><green>137</green><blue>142</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7047</colorName><RGB8><red>207</red><green>208</green><blue>207</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 7048</colorName><RGB8><red>136</red><green>129</green><blue>117</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 8000</colorName><RGB8><red>136</red><green>113</green><blue>66</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8001</colorName><RGB8><red>156</red><green>107</green><blue>48</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8002</colorName><RGB8><red>123</red><green>81</green><blue>65</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8003</colorName><RGB8><red>128</red><green>84</green><blue>47</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8004</colorName><RGB8><red>143</red><green>78</green><blue>53</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8007</colorName><RGB8><red>111</red><green>74</green><blue>47</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8008</colorName><RGB8><red>111</red><green>79</green><blue>40</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 8011</colorName><RGB8><red>90</red><green>58</green><blue>41</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8012</colorName><RGB8><red>103</red><green>56</green><blue>49</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8014</colorName><RGB8><red>73</red><green>57</green><blue>45</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8015</colorName><RGB8><red>99</red><green>58</green><blue>52</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8016</colorName><RGB8><red>76</red><green>47</green><blue>38</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8017</colorName><RGB8><red>68</red><green>50</green><blue>45</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8019</colorName><RGB8><red>63</red><green>58</green><blue>58</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 8022</colorName><RGB8><red>33</red><green>31</green><blue>32</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8023</colorName><RGB8><red>166</red><green>94</green><blue>47</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8024</colorName><RGB8><red>121</red><green>85</green><blue>60</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8025</colorName><RGB8><red>117</red><green>92</green><blue>73</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8028</colorName><RGB8><red>78</red><green>59</green><blue>43</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 8029</colorName><RGB8><red>119</red><green>60</green><blue>39</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 9001</colorName><RGB8><red>239</red><green>235</green><blue>220</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 9002</colorName><RGB8><red>221</red><green>222</green><blue>212</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 9003</colorName><RGB8><red>255</red><green>255</green><blue>255</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 9004</colorName><RGB8><red>46</red><green>48</green><blue>50</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 9005</colorName><RGB8><red>19</red><green>21</green><blue>22</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 9006</colorName><RGB8><red>165</red><green>168</green><blue>166</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 9007</colorName><RGB8><red>143</red><green>143</green><blue>140</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 9010</colorName><RGB8><red>247</red><green>249</green><blue>239</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 9011</colorName><RGB8><red>28</red><green>28</green><blue>28</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 9012</colorName><RGB8><red>255</red><green>253</green><blue>230</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 9016</colorName><RGB8><red>247</red><green>251</green><blue>245</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 9017</colorName><RGB8><red>42</red><green>45</green><blue>47</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 9018</colorName><RGB8><red>207</red><green>211</green><blue>205</blue></RGB8></colorEntry>
<colorEntry><colorName>RAL 9022</colorName><RGB8><red>133</red><green>133</green><blue>131</blue></RGB8></colorEntry>
</colorPage>
<colorPage>
<colorEntry><colorName>RAL 9023</colorName><RGB8><red>126</red><green>129</green><blue>130</blue></RGB8></colorEntry>
</colorPage>
</colorBook>
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Phosphor Icons
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+2
View File
@@ -0,0 +1,2 @@
<!-- Phosphor Icons "eyedropper" (regular), MIT License, Copyright (c) 2023 Phosphor Icons, https://github.com/phosphor-icons/core. See LICENSE-phosphor.txt. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="#B4B6B9"><path d="M224,67.3a35.79,35.79,0,0,0-11.26-25.66c-14-13.28-36.72-12.78-50.62,1.13L142.8,62.2a24,24,0,0,0-33.14.77l-9,9a16,16,0,0,0,0,22.64l2,2.06-51,51a39.75,39.75,0,0,0-10.53,38l-8,18.41A13.68,13.68,0,0,0,36,219.3a15.92,15.92,0,0,0,17.71,3.35L71.23,215a39.89,39.89,0,0,0,37.06-10.75l51-51,2.06,2.06a16,16,0,0,0,22.62,0l9-9a24,24,0,0,0,.74-33.18l19.75-19.87A35.75,35.75,0,0,0,224,67.3ZM97,193a24,24,0,0,1-24,6,8,8,0,0,0-5.55.31l-18.1,7.91L57,189.41a8,8,0,0,0,.25-5.75A23.88,23.88,0,0,1,63,159l51-51,33.94,34ZM202.13,82l-25.37,25.52a8,8,0,0,0,0,11.3l4.89,4.89a8,8,0,0,1,0,11.32l-9,9L112,83.26l9-9a8,8,0,0,1,11.31,0l4.89,4.89a8,8,0,0,0,11.33,0l24.94-25.09c7.81-7.82,20.5-8.18,28.29-.81a20,20,0,0,1,.39,28.7Z"/></svg>

After

Width:  |  Height:  |  Size: 954 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#B4B6B9" stroke-width="1.8" stroke-linejoin="round"><rect x="4" y="4" width="16" height="4" rx="1"/><rect x="4" y="10" width="16" height="4" rx="1"/><rect x="4" y="16" width="16" height="4" rx="1"/></svg>

After

Width:  |  Height:  |  Size: 285 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#B4B6B9" stroke-width="2" stroke-linecap="round"><circle cx="10.5" cy="10.5" r="6"/><path d="M15 15l5 5"/></svg>

After

Width:  |  Height:  |  Size: 193 B

+20 -4
View File
@@ -1430,15 +1430,31 @@ common =
.dismiss = إغلاق
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = لا توجد ألوان حديثة بعد
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = سداسي عشري
.nearest-index-color = أقرب فهرس
.exact-index-match = تطابق تام مع الفهرس
.color-recently-used = المستخدمة مؤخرًا
.color-most-used = الأكثر استخدامًا
.color-number = رقم اللون
.color-name-az = الاسم أ–ي
.color-name-za = الاسم ي–أ
.color-sort = فرز
.color-no-drawing-colors = لا توجد ألوان في هذا الرسم بعد
.color-books = كتب الألوان
.color-no-books = لم يتم تحميل كتب ألوان
.color-load-book = تحميل كتاب ألوان (.acb)
.color-book-label = كتاب الألوان
.color-rgb-equivalent = مكافئ RGB
.color-no-match = لا توجد ألوان مطابقة
.color-not-a-book = ليس كتاب ألوان
.color-book-encrypted = كتاب الألوان هذا مشفر ولا يمكن قراءته
.color-book-empty = لا يحتوي كتاب الألوان على ألوان
.color-book-locations = مواقع كتب الألوان
.cad-color-index-aci-10-249 = فهرس ألوان ACI 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = المستخدمة مؤخرًا
.isometric-drafting = الرسم متساوي القياس
.f5-cycles-the-active-plane = يبدّل F5 المستوى النشط بالتتابع.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1417,15 +1417,31 @@ common =
.dismiss = Затваряне
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Все още няма скорошни цветове
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = Най-близък индекс
.exact-index-match = Точно съвпадение с индекс
.color-recently-used = Наскоро използвани
.color-most-used = Най-често използвани
.color-number = Номер на цвят
.color-name-az = Име А–Я
.color-name-za = Име Я–А
.color-sort = Сортиране
.color-no-drawing-colors = Все още няма цветове в този чертеж
.color-books = Цветови каталози
.color-no-books = Няма заредени цветови каталози
.color-load-book = Зареждане на цветови каталог (.acb)
.color-book-label = Цветови каталог
.color-rgb-equivalent = RGB еквивалент
.color-no-match = Няма съвпадащи цветове
.color-not-a-book = Не е цветови каталог
.color-book-encrypted = Този цветови каталог е шифрован и не може да бъде прочетен
.color-book-empty = Цветовият каталог няма цветове
.color-book-locations = Местоположения на цветови каталози
.cad-color-index-aci-10-249 = ACI Цветови индекс 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Последно използвани
.isometric-drafting = Изометрично чертане
.f5-cycles-the-active-plane = F5 превключва последователно активната равнина.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1417,15 +1417,31 @@ common =
.dismiss = Zavřít
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Zatím žádné nedávné barvy
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = Nejbližší index
.exact-index-match = Přesná shoda s indexem
.color-recently-used = Naposledy použité
.color-most-used = Nejpoužívanější
.color-number = Číslo barvy
.color-name-az = Název A–Z
.color-name-za = Název Z–A
.color-sort = Řadit
.color-no-drawing-colors = V tomto výkresu zatím nejsou žádné barvy
.color-books = Barevné vzorníky
.color-no-books = Nejsou načteny žádné barevné vzorníky
.color-load-book = Načíst barevný vzorník (.acb)
.color-book-label = Barevný vzorník
.color-rgb-equivalent = Ekvivalent RGB
.color-no-match = Žádné odpovídající barvy
.color-not-a-book = Nejde o barevný vzorník
.color-book-encrypted = Tento barevný vzorník je zašifrovaný a nelze jej přečíst
.color-book-empty = Barevný vzorník neobsahuje žádné barvy
.color-book-locations = Umístění barevných vzorníků
.cad-color-index-aci-10-249 = Barevný index ACI 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Nedávno použité
.isometric-drafting = Izometrické kreslení
.f5-cycles-the-active-plane = F5 cyklicky přepíná aktivní rovinu.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1418,15 +1418,31 @@ common =
.dismiss = Schließen
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Noch keine zuletzt verwendeten Farben
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = Nächster Index
.exact-index-match = Exakter Index-Treffer
.color-recently-used = Zuletzt verwendet
.color-most-used = Am häufigsten verwendet
.color-number = Farbnummer
.color-name-az = Name A–Z
.color-name-za = Name Z–A
.color-sort = Sortieren
.color-no-drawing-colors = Noch keine Farben in dieser Zeichnung
.color-books = Farbbücher
.color-no-books = Keine Farbbücher geladen
.color-load-book = Farbbuch laden (.acb)
.color-book-label = Farbbuch
.color-rgb-equivalent = RGB-Entsprechung
.color-no-match = Keine passenden Farben
.color-not-a-book = Kein Farbbuch
.color-book-encrypted = Dieses Farbbuch ist verschlüsselt und kann nicht gelesen werden
.color-book-empty = Das Farbbuch enthält keine Farben
.color-book-locations = Speicherorte für Farbbücher
.cad-color-index-aci-10-249 = ACI Farbindex 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Zuletzt verwendet
.isometric-drafting = Isometrisches Zeichnen
.f5-cycles-the-active-plane = F5 wechselt die aktive Ebene der Reihe nach.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1419,15 +1419,31 @@ common =
.dismiss = Κλείσιμο
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Δεν υπάρχουν ακόμη πρόσφατα χρώματα
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = Πλησιέστερος δείκτης
.exact-index-match = Ακριβής αντιστοίχιση δείκτη
.color-recently-used = Πρόσφατα χρησιμοποιημένα
.color-most-used = Πιο συχνά χρησιμοποιημένα
.color-number = Αριθμός χρώματος
.color-name-az = Όνομα Α–Ω
.color-name-za = Όνομα Ω–Α
.color-sort = Ταξινόμηση
.color-no-drawing-colors = Δεν υπάρχουν ακόμη χρώματα σε αυτό το σχέδιο
.color-books = Βιβλία χρωμάτων
.color-no-books = Δεν έχουν φορτωθεί βιβλία χρωμάτων
.color-load-book = Φόρτωση βιβλίου χρωμάτων (.acb)
.color-book-label = Βιβλίο χρωμάτων
.color-rgb-equivalent = Ισοδύναμο RGB
.color-no-match = Κανένα χρώμα δεν ταιριάζει
.color-not-a-book = Δεν είναι βιβλίο χρωμάτων
.color-book-encrypted = Αυτό το βιβλίο χρωμάτων είναι κρυπτογραφημένο και δεν μπορεί να διαβαστεί
.color-book-empty = Το βιβλίο χρωμάτων δεν έχει χρώματα
.color-book-locations = Θέσεις βιβλίων χρωμάτων
.cad-color-index-aci-10-249 = Δείκτης χρώματος ACI 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Πρόσφατα χρησιμοποιημένα
.isometric-drafting = Ισομετρική σχεδίαση
.f5-cycles-the-active-plane = Το F5 εναλλάσσει το ενεργό επίπεδο.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1418,15 +1418,31 @@ common =
.dismiss = Dismiss
.value-8a8de8 = ?
.select-color-8dddf1 = Select Color...
.no-recent-colors-yet = No recent colors yet
.true-color-rgb = True Color RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = Index Color
.true-color = True Color
.standard-colors = Standard colors
.color-hex = Hex
.nearest-index-color = Nearest index
.exact-index-match = Exact index match
.color-recently-used = Recently used
.color-most-used = Most used
.color-number = Color number
.color-name-az = Name A–Z
.color-name-za = Name Z–A
.color-sort = Sort
.color-no-drawing-colors = No colors in this drawing yet
.color-books = Color Books
.color-no-books = No color books loaded
.color-load-book = Load color book (.acb)
.color-book-label = Color book
.color-rgb-equivalent = RGB equivalent
.color-no-match = No colors match
.color-not-a-book = Not a color book
.color-book-encrypted = This color book is encrypted and cannot be read
.color-book-empty = The color book has no colors
.color-book-locations = Color book locations
.cad-color-index-aci-10-249 = CAD Color Index (ACI) 10–249
.color-family-shade-intensity = Color family → · Shade / intensity ↓
.grayscale-250-255 = Grayscale 250–255
.recently-used = Recently used
.isometric-drafting = Isometric drafting
.f5-cycles-the-active-plane = F5 cycles the active plane.
.units-5430a1 = Units…
+20 -4
View File
@@ -1418,15 +1418,31 @@ common =
.dismiss = Cerrar
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Aún no hay colores recientes
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = Índice más cercano
.exact-index-match = Coincide con un índice
.color-recently-used = Usados recientemente
.color-most-used = Más usados
.color-number = Número de color
.color-name-az = Nombre A–Z
.color-name-za = Nombre Z–A
.color-sort = Ordenar
.color-no-drawing-colors = Aún no hay colores en este dibujo
.color-books = Libros de colores
.color-no-books = No hay libros de colores cargados
.color-load-book = Cargar libro de colores (.acb)
.color-book-label = Libro de colores
.color-rgb-equivalent = Equivalente RGB
.color-no-match = Ningún color coincide
.color-not-a-book = No es un libro de colores
.color-book-encrypted = Este libro de colores está cifrado y no se puede leer
.color-book-empty = El libro de colores no tiene colores
.color-book-locations = Ubicaciones de libros de colores
.cad-color-index-aci-10-249 = Índice de color ACI 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Usados recientemente
.isometric-drafting = Dibujo isométrico
.f5-cycles-the-active-plane = F5 alterna el plano activo.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1417,15 +1417,31 @@ common =
.dismiss = Sulje
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Ei vielä viimeksi käytettyjä värejä
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = Lähin indeksi
.exact-index-match = Täsmälleen indeksiväri
.color-recently-used = Viimeksi käytetyt
.color-most-used = Eniten käytetyt
.color-number = Värin numero
.color-name-az = Nimi A–Ö
.color-name-za = Nimi Ö–A
.color-sort = Lajittele
.color-no-drawing-colors = Tässä piirustuksessa ei ole vielä värejä
.color-books = Värikirjat
.color-no-books = Värikirjoja ei ole ladattu
.color-load-book = Lataa värikirja (.acb)
.color-book-label = Värikirja
.color-rgb-equivalent = RGB-vastine
.color-no-match = Ei vastaavia värejä
.color-not-a-book = Ei värikirja
.color-book-encrypted = Tämä värikirja on salattu eikä sitä voi lukea
.color-book-empty = Värikirjassa ei ole värejä
.color-book-locations = Värikirjojen sijainnit
.cad-color-index-aci-10-249 = ACI väriindeksi 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Viimeksi käytetyt
.isometric-drafting = Isometrinen piirtäminen
.f5-cycles-the-active-plane = F5 vaihtaa aktiivista tasoa.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1418,15 +1418,31 @@ common =
.dismiss = Fermer
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Aucune couleur récente
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = Index le plus proche
.exact-index-match = Correspondance exacte d'index
.color-recently-used = Utilisées récemment
.color-most-used = Les plus utilisées
.color-number = Numéro de couleur
.color-name-az = Nom A–Z
.color-name-za = Nom Z–A
.color-sort = Trier
.color-no-drawing-colors = Aucune couleur dans ce dessin pour l'instant
.color-books = Carnets de couleurs
.color-no-books = Aucun carnet de couleurs chargé
.color-load-book = Charger un carnet de couleurs (.acb)
.color-book-label = Carnet de couleurs
.color-rgb-equivalent = Équivalent RVB
.color-no-match = Aucune couleur correspondante
.color-not-a-book = Ce n'est pas un carnet de couleurs
.color-book-encrypted = Ce carnet de couleurs est chiffré et ne peut pas être lu
.color-book-empty = Le carnet de couleurs ne contient aucune couleur
.color-book-locations = Emplacements des carnets de couleurs
.cad-color-index-aci-10-249 = ACI Indice de couleur 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Utilisés récemment
.isometric-drafting = Dessin isométrique
.f5-cycles-the-active-plane = F5 fait défiler les plans actifs.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1406,15 +1406,31 @@ common =
.dismiss = बंद करें
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = अभी कोई हालिया रंग नहीं है
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = हेक्स
.nearest-index-color = निकटतम इंडेक्स
.exact-index-match = इंडेक्स से सटीक मेल
.color-recently-used = हाल ही में उपयोग किए गए
.color-most-used = सबसे अधिक उपयोग किए गए
.color-number = रंग संख्या
.color-name-az = नाम A–Z
.color-name-za = नाम Z–A
.color-sort = क्रमबद्ध करें
.color-no-drawing-colors = इस ड्रॉइंग में अभी तक कोई रंग नहीं है
.color-books = रंग पुस्तकें
.color-no-books = कोई रंग पुस्तक लोड नहीं है
.color-load-book = रंग पुस्तक लोड करें (.acb)
.color-book-label = रंग पुस्तक
.color-rgb-equivalent = RGB समतुल्य
.color-no-match = कोई रंग मेल नहीं खाता
.color-not-a-book = यह रंग पुस्तक नहीं है
.color-book-encrypted = यह रंग पुस्तक एन्क्रिप्टेड है और पढ़ी नहीं जा सकती
.color-book-empty = रंग पुस्तक में कोई रंग नहीं है
.color-book-locations = रंग पुस्तकों के स्थान
.cad-color-index-aci-10-249 = ACI रंग सूचकांक 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = हाल में उपयोग किए गए
.isometric-drafting = सममितीय आरेखन
.f5-cycles-the-active-plane = F5 सक्रिय तल को क्रम से बदलता है।
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1422,15 +1422,31 @@ common =
.dismiss = Bezárás
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Még nincsenek legutóbbi színek
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.color } ({ common.standard })
.color-hex = Hex
.nearest-index-color = Legközelebbi index
.exact-index-match = Pontos indexegyezés
.color-recently-used = Legutóbb használt
.color-most-used = Leggyakrabban használt
.color-number = Színszám
.color-name-az = Név A–Z
.color-name-za = Név Z–A
.color-sort = Rendezés
.color-no-drawing-colors = Ebben a rajzban még nincsenek színek
.color-books = Színkönyvek
.color-no-books = Nincs betöltött színkönyv
.color-load-book = Színkönyv betöltése (.acb)
.color-book-label = Színkönyv
.color-rgb-equivalent = RGB-megfelelő
.color-no-match = Nincs egyező szín
.color-not-a-book = Nem színkönyv
.color-book-encrypted = Ez a színkönyv titkosított, és nem olvasható
.color-book-empty = A színkönyv nem tartalmaz színeket
.color-book-locations = Színkönyvek helye
.cad-color-index-aci-10-249 = ACI színindex 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Legutóbb használt
.isometric-drafting = Izometrikus rajzolás
.f5-cycles-the-active-plane = Az F5 váltja az aktív síkot.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1417,15 +1417,31 @@ common =
.dismiss = Chiudi
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Nessun colore recente
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = Indice più vicino
.exact-index-match = Corrispondenza esatta indice
.color-recently-used = Usati di recente
.color-most-used = Più usati
.color-number = Numero colore
.color-name-az = Nome A–Z
.color-name-za = Nome Z–A
.color-sort = Ordina
.color-no-drawing-colors = Ancora nessun colore in questo disegno
.color-books = Album colori
.color-no-books = Nessun album colori caricato
.color-load-book = Carica album colori (.acb)
.color-book-label = Album colori
.color-rgb-equivalent = Equivalente RGB
.color-no-match = Nessun colore corrispondente
.color-not-a-book = Non è un album colori
.color-book-encrypted = Questo album colori è crittografato e non può essere letto
.color-book-empty = L'album colori non contiene colori
.color-book-locations = Percorsi degli album colori
.cad-color-index-aci-10-249 = ACI Indice colori 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Usati di recente
.isometric-drafting = Disegno isometrico
.f5-cycles-the-active-plane = F5 alterna il piano attivo.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1408,15 +1408,31 @@ common =
.dismiss = 閉じる
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = 最近使った色はありません
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = 16進
.nearest-index-color = 最も近いインデックス
.exact-index-match = インデックスと完全一致
.color-recently-used = 最近使用した色
.color-most-used = 使用頻度の高い色
.color-number = 色番号
.color-name-az = 名前 (昇順)
.color-name-za = 名前 (降順)
.color-sort = 並べ替え
.color-no-drawing-colors = この図面にはまだ色がありません
.color-books = カラー ブック
.color-no-books = カラー ブックが読み込まれていません
.color-load-book = カラー ブックを読み込む (.acb)
.color-book-label = カラー ブック
.color-rgb-equivalent = RGB 相当値
.color-no-match = 一致する色がありません
.color-not-a-book = カラー ブックではありません
.color-book-encrypted = このカラー ブックは暗号化されているため読み込めません
.color-book-empty = カラー ブックに色がありません
.color-book-locations = カラー ブックの場所
.cad-color-index-aci-10-249 = ACI カラーインデックス 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = 最近使用した色
.isometric-drafting = アイソメ図作成
.f5-cycles-the-active-plane = F5でアクティブな平面を順に切り替えます。
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1417,15 +1417,31 @@ common =
.dismiss = 닫기
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = 최근 사용한 색상이 없습니다
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = 16진수
.nearest-index-color = 가장 가까운 인덱스
.exact-index-match = 인덱스와 정확히 일치
.color-recently-used = 최근 사용
.color-most-used = 가장 많이 사용
.color-number = 색상 번호
.color-name-az = 이름 오름차순
.color-name-za = 이름 내림차순
.color-sort = 정렬
.color-no-drawing-colors = 이 도면에는 아직 색상이 없습니다
.color-books = 색상표
.color-no-books = 로드된 색상표가 없습니다
.color-load-book = 색상표 로드 (.acb)
.color-book-label = 색상표
.color-rgb-equivalent = RGB 등가
.color-no-match = 일치하는 색상이 없습니다
.color-not-a-book = 색상표가 아닙니다
.color-book-encrypted = 이 색상표는 암호화되어 있어 읽을 수 없습니다
.color-book-empty = 색상표에 색상이 없습니다
.color-book-locations = 색상표 위치
.cad-color-index-aci-10-249 = ACI 컬러 인덱스 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = 최근 사용
.isometric-drafting = 등각 제도
.f5-cycles-the-active-plane = F5 키로 활성 평면을 순환 전환합니다.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1423,15 +1423,31 @@ common =
.dismiss = ປິດອອກ
.value-8a8de8 = ?
.select-color-8dddf1 = ເລືອກສີ...
.no-recent-colors-yet = ຍັງບໍ່ມີສີຫຼ້າສຸດ
.true-color-rgb = True Color RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = ສີດັດຊະນີ
.true-color = True Color
.standard-colors = ສີມາດຕະຖານ
.color-hex = Hex
.nearest-index-color = ດັດຊະນີທີ່ໃກ້ທີ່ສຸດ
.exact-index-match = ກົງກັບດັດຊະນີພໍດີ
.color-recently-used = ໃຊ້ຫຼ້າສຸດ
.color-most-used = ໃຊ້ຫຼາຍທີ່ສຸດ
.color-number = ເລກສີ
.color-name-az = ຊື່ A–Z
.color-name-za = ຊື່ Z–A
.color-sort = ຈັດລຽງ
.color-no-drawing-colors = ຍັງບໍ່ມີສີໃນແບບແຕ້ມນີ້
.color-books = ປຶ້ມສີ
.color-no-books = ບໍ່ໄດ້ໂຫຼດປຶ້ມສີ
.color-load-book = ໂຫຼດປຶ້ມສີ (.acb)
.color-book-label = ປຶ້ມສີ
.color-rgb-equivalent = ທຽບເທົ່າ RGB
.color-no-match = ບໍ່ມີສີທີ່ກົງກັນ
.color-not-a-book = ບໍ່ແມ່ນປຶ້ມສີ
.color-book-encrypted = ປຶ້ມສີນີ້ຖືກເຂົ້າລະຫັດ ແລະອ່ານບໍ່ໄດ້
.color-book-empty = ປຶ້ມສີບໍ່ມີສີ
.color-book-locations = ທີ່ຕັ້ງປຶ້ມສີ
.cad-color-index-aci-10-249 = CAD Color Index (ACI) 10–249
.color-family-shade-intensity = ກຸ່ມສີ → · ລະດັບສີ / ຄວາມເຂັ້ມ ↓
.grayscale-250-255 = ລະດັບສີເທົາ 250–255
.recently-used = ໃຊ້ຫຼ້າສຸດ
.isometric-drafting = ການຂຽນແບບໄອໂຊເມຕຣິກ
.f5-cycles-the-active-plane = F5 ປ່ຽນລະນາບທີ່ໃຊ້ງານວຽນໄປ.
.units-5430a1 = ຫົວໜ່ວຍ…
+20 -4
View File
@@ -1416,15 +1416,31 @@ common =
.dismiss = Sluiten
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Nog geen recente kleuren
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = Dichtstbijzijnde index
.exact-index-match = Exacte indexkleur
.color-recently-used = Recent gebruikt
.color-most-used = Meest gebruikt
.color-number = Kleurnummer
.color-name-az = Naam A–Z
.color-name-za = Naam Z–A
.color-sort = Sorteren
.color-no-drawing-colors = Nog geen kleuren in deze tekening
.color-books = Kleurboeken
.color-no-books = Geen kleurboeken geladen
.color-load-book = Kleurboek laden (.acb)
.color-book-label = Kleurboek
.color-rgb-equivalent = RGB-equivalent
.color-no-match = Geen kleuren gevonden
.color-not-a-book = Geen kleurboek
.color-book-encrypted = Dit kleurboek is versleuteld en kan niet worden gelezen
.color-book-empty = Het kleurboek bevat geen kleuren
.color-book-locations = Locaties van kleurboeken
.cad-color-index-aci-10-249 = ACI Kleurindex 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Recent gebruikt
.isometric-drafting = Isometrisch tekenen
.f5-cycles-the-active-plane = F5 wisselt het actieve vlak.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1417,15 +1417,31 @@ common =
.dismiss = Zamknij
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Brak ostatnio używanych kolorów
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = Najbliższy indeks
.exact-index-match = Dokładne dopasowanie indeksu
.color-recently-used = Ostatnio używane
.color-most-used = Najczęściej używane
.color-number = Numer koloru
.color-name-az = Nazwa A–Z
.color-name-za = Nazwa Z–A
.color-sort = Sortuj
.color-no-drawing-colors = Brak kolorów w tym rysunku
.color-books = Wzorniki kolorów
.color-no-books = Nie wczytano wzorników kolorów
.color-load-book = Wczytaj wzornik kolorów (.acb)
.color-book-label = Wzornik kolorów
.color-rgb-equivalent = Odpowiednik RGB
.color-no-match = Brak pasujących kolorów
.color-not-a-book = To nie jest wzornik kolorów
.color-book-encrypted = Ten wzornik kolorów jest zaszyfrowany i nie można go odczytać
.color-book-empty = Wzornik kolorów nie zawiera kolorów
.color-book-locations = Lokalizacje wzorników kolorów
.cad-color-index-aci-10-249 = Indeks kolorów ACI 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Ostatnio używane
.isometric-drafting = Rysowanie izometryczne
.f5-cycles-the-active-plane = F5 przełącza aktywną płaszczyznę.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1418,15 +1418,31 @@ common =
.dismiss = Fechar
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Nenhuma cor recente ainda
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = Índice mais próximo
.exact-index-match = Corresponde exatamente ao índice
.color-recently-used = Usadas recentemente
.color-most-used = Mais usadas
.color-number = Número da cor
.color-name-az = Nome A–Z
.color-name-za = Nome Z–A
.color-sort = Ordenar
.color-no-drawing-colors = Ainda não há cores neste desenho
.color-books = Catálogos de cores
.color-no-books = Nenhum catálogo de cores carregado
.color-load-book = Carregar catálogo de cores (.acb)
.color-book-label = Catálogo de cores
.color-rgb-equivalent = Equivalente RGB
.color-no-match = Nenhuma cor corresponde
.color-not-a-book = Não é um catálogo de cores
.color-book-encrypted = Este catálogo de cores é criptografado e não pode ser lido
.color-book-empty = O catálogo de cores não tem cores
.color-book-locations = Locais dos catálogos de cores
.cad-color-index-aci-10-249 = Índice de cor ACI 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Usados recentemente
.isometric-drafting = Desenho isométrico
.f5-cycles-the-active-plane = F5 alterna o plano ativo.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1421,15 +1421,31 @@ common =
.dismiss = Закрыть
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Недавно использованных цветов пока нет
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = Ближайший индекс
.exact-index-match = Точное совпадение с индексом
.color-recently-used = Недавно использованные
.color-most-used = Чаще всего используемые
.color-number = Номер цвета
.color-name-az = Имя А–Я
.color-name-za = Имя Я–А
.color-sort = Сортировка
.color-no-drawing-colors = В этом чертеже пока нет цветов
.color-books = Альбомы цветов
.color-no-books = Альбомы цветов не загружены
.color-load-book = Загрузить альбом цветов (.acb)
.color-book-label = Альбом цветов
.color-rgb-equivalent = Эквивалент RGB
.color-no-match = Нет подходящих цветов
.color-not-a-book = Это не альбом цветов
.color-book-encrypted = Этот альбом цветов зашифрован и не может быть прочитан
.color-book-empty = В альбоме цветов нет цветов
.color-book-locations = Расположение альбомов цветов
.cad-color-index-aci-10-249 = Индекс цвета ACI 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Недавно использованные
.isometric-drafting = Изометрическое черчение
.f5-cycles-the-active-plane = F5 циклически переключает активную плоскость.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1404,15 +1404,31 @@ common =
.dismiss = Kapat
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = Henüz son kullanılan renk yok
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = Hex
.nearest-index-color = En yakın indeks
.exact-index-match = Tam indeks eşleşmesi
.color-recently-used = Son kullanılanlar
.color-most-used = En çok kullanılanlar
.color-number = Renk numarası
.color-name-az = Ad A–Z
.color-name-za = Ad Z–A
.color-sort = Sırala
.color-no-drawing-colors = Bu çizimde henüz renk yok
.color-books = Renk kitapları
.color-no-books = Yüklenmiş renk kitabı yok
.color-load-book = Renk kitabı yükle (.acb)
.color-book-label = Renk kitabı
.color-rgb-equivalent = RGB karşılığı
.color-no-match = Eşleşen renk yok
.color-not-a-book = Bir renk kitabı değil
.color-book-encrypted = Bu renk kitabı şifreli ve okunamıyor
.color-book-empty = Renk kitabında renk yok
.color-book-locations = Renk kitabı konumları
.cad-color-index-aci-10-249 = ACI renk dizini 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = Son kullanılanlar
.isometric-drafting = İzometrik çizim
.f5-cycles-the-active-plane = F5 etkin düzlemi sırayla değiştirir.
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1405,15 +1405,31 @@ common =
.dismiss = 关闭
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = 尚无最近使用的颜色
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = 十六进制
.nearest-index-color = 最接近的索引色
.exact-index-match = 与索引色完全匹配
.color-recently-used = 最近使用
.color-most-used = 最常使用
.color-number = 颜色编号
.color-name-az = 名称 A–Z
.color-name-za = 名称 Z–A
.color-sort = 排序
.color-no-drawing-colors = 此图形中还没有颜色
.color-books = 配色系统
.color-no-books = 未加载配色系统
.color-load-book = 加载配色系统 (.acb)
.color-book-label = 配色系统
.color-rgb-equivalent = RGB 等效值
.color-no-match = 没有匹配的颜色
.color-not-a-book = 不是配色系统
.color-book-encrypted = 此配色系统已加密,无法读取
.color-book-empty = 配色系统中没有颜色
.color-book-locations = 配色系统位置
.cad-color-index-aci-10-249 = ACI 颜色索引 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = 最近使用
.isometric-drafting = 等轴测绘图
.f5-cycles-the-active-plane = F5 循环切换活动平面。
.units-5430a1 = { common.units }…
+20 -4
View File
@@ -1417,15 +1417,31 @@ common =
.dismiss = 關閉
.value-8a8de8 = ?
.select-color-8dddf1 = { common.select-color }…
.no-recent-colors-yet = 尚無最近使用的顏色
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
.index-color = { common.color } (ACI)
.true-color = { common.color } (RGB)
.standard-colors = { common.standard } · { common.color }
.color-hex = 十六進位
.nearest-index-color = 最接近的索引色
.exact-index-match = 與索引色完全相符
.color-recently-used = 最近使用
.color-most-used = 最常使用
.color-number = 顏色編號
.color-name-az = 名稱 A–Z
.color-name-za = 名稱 Z–A
.color-sort = 排序
.color-no-drawing-colors = 此圖面中還沒有顏色
.color-books = 配色系統
.color-no-books = 未載入配色系統
.color-load-book = 載入配色系統 (.acb)
.color-book-label = 配色系統
.color-rgb-equivalent = RGB 等效值
.color-no-match = 沒有相符的顏色
.color-not-a-book = 不是配色系統
.color-book-encrypted = 此配色系統已加密,無法讀取
.color-book-empty = 配色系統中沒有顏色
.color-book-locations = 配色系統位置
.cad-color-index-aci-10-249 = ACI 顏色索引 10–249
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
.grayscale-250-255 = { common.grayscale } 250–255
.recently-used = 最近使用
.isometric-drafting = 等角製圖
.f5-cycles-the-active-plane = F5 循環切換作用中平面。
.units-5430a1 = { common.units }…
+10 -2
View File
@@ -358,7 +358,11 @@ impl OpenCADStudio {
if layer != "0" || entity.as_entity().layer().is_empty() {
entity.as_entity_mut().set_layer(layer);
}
crate::scene::view::dispatch::apply_color(&mut entity, self.ribbon.active_color);
crate::scene::view::dispatch::apply_named_color(
&mut entity,
self.ribbon.active_color,
self.ribbon.active_color_name(),
);
crate::scene::view::dispatch::apply_common_prop(
&mut entity,
"linetype",
@@ -476,7 +480,11 @@ impl OpenCADStudio {
if layer != "0" || entity.as_entity().layer().is_empty() {
entity.as_entity_mut().set_layer(layer);
}
crate::scene::view::dispatch::apply_color(&mut entity, self.ribbon.active_color);
crate::scene::view::dispatch::apply_named_color(
&mut entity,
self.ribbon.active_color,
self.ribbon.active_color_name(),
);
crate::scene::view::dispatch::apply_common_prop(
&mut entity,
"linetype",
+2 -2
View File
@@ -391,8 +391,8 @@ impl OpenCADStudio {
.location(id)
.map_or(crate::app::config::DockSide::Right, |(side, _)| side),
matches!(
self.color_pick_target,
Some((crate::app::ColorPickTarget::Gradient(_), _))
self.color_picker.as_ref().map(|picker| &picker.target),
Some(crate::app::ColorPickTarget::Gradient(_))
),
),
crate::ui::dock::PanelId::BlockPalette => {
+3
View File
@@ -290,6 +290,8 @@ pub(super) struct DocumentTab {
/// Model-space corners of the last crossing window drawn with no command
/// running: STRETCH on that selection stretches through it (#1068).
pub(super) last_crossing_window: Option<([f64; 2], [f64; 2])>,
/// Colours picked in this drawing this session, newest first. Not saved.
pub(super) recent_colors: Vec<codec::types::Color>,
}
impl DocumentTab {
@@ -697,6 +699,7 @@ impl DocumentTab {
suspended_cmd: None,
transparent_resume: false,
last_crossing_window: None,
recent_colors: Vec::new(),
}
}
+29 -36
View File
@@ -952,9 +952,6 @@ pub(super) struct OpenCADStudio {
pub(crate) sheet_set_pending_layout: Option<(String, String)>,
/// The subset / set Import Layout as Sheet adds to, while its file picker is open.
pub(crate) sheet_set_import_parent: Option<String>,
/// Which viewport background the colour wheel is editing, or `None` when
/// it is closed. One slot, because only one wheel can be open at a time.
pub(crate) bg_picker: Option<BgTarget>,
/// General edge-stack dock layout for the side panels.
pub(crate) dock: crate::ui::dock::DockState,
/// Pallet revealed although it auto-hides (its edge's group, or its
@@ -1074,13 +1071,14 @@ pub(super) struct OpenCADStudio {
#[cfg(target_arch = "wasm32")]
web_autosave_clear_after: Vec<String>,
// ── Floating panel windows ────────────────────────────────────────────
/// Active `iced_aw` colour picker: destination plus its initial colour.
color_pick_target: Option<(ColorPickTarget, AcadColor)>,
/// Visible page in the shared CAD colour picker.
color_picker_tab: ColorPickerTab,
/// The open Select Color panel, if any.
color_picker: Option<crate::ui::color_select::ColorPicker>,
/// The built-in colour book and those in the colour book folder, read
/// lazily when the Select Color panel first opens.
color_books: std::sync::Arc<Vec<crate::ui::color_select::book::ColorBook>>,
color_books_read: bool,
/// Recently committed real colours, newest first.
/// ACI and True Color remain semantically distinct even when their RGB matches.
recent_colors: Vec<AcadColor>,
/// The open in-canvas modal dialog, if any (Plan B: shared overlay instead
/// of OS windows).
active_modal: Option<ModalKind>,
@@ -1788,21 +1786,19 @@ pub struct SaveOutcome {
refreshed_preview: Option<Option<codec::Preview>>,
result: Result<(), crate::io::SaveFailure>,
}
/// Which viewport background a colour-wheel session is editing.
/// Which Options colour the interface colour picker is editing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BgTarget {
Model,
Paper,
Desk,
pub enum UiColor {
ModelBackground,
PaperBackground,
DeskBackground,
/// One of the six theme colours, by its row in Options.
Theme(usize),
Crosshair,
SnapMarker,
CommandText,
}
/// Active page in the shared CAD colour picker.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ColorPickerTab {
#[default]
Index,
TrueColor,
}
/// Where a colour chosen in the standalone palette window should be applied.
#[derive(Debug, Clone)]
pub enum ColorPickTarget {
@@ -1828,6 +1824,8 @@ pub enum ColorPickTarget {
LayerState(usize),
/// The MText editor's selection (or global) colour.
MText,
/// An Options interface colour (true colour only).
Interface(UiColor),
/// A colour of the view Background dialog: 0 solid, 1 top, 2 middle, 3 bottom.
ViewBackground(u8),
}
@@ -2596,12 +2594,6 @@ pub enum Message {
ModelSpaceModeChanged(config::ModelSpaceMode),
/// Change Model Space custom background color as hex or empty for default.
ModelSpaceBgChanged(String),
/// Open the colour wheel on one of the viewport backgrounds.
BgPickerOpen(BgTarget),
/// Dismiss the wheel, changing nothing.
BgPickerCancel,
/// Accept the wheel's colour for whichever background it was opened on.
BgPickerSubmit(iced::Color),
/// Change Paper Space custom sheet background color as hex or empty for default.
PaperSpaceBgChanged(String),
/// Change Paper Space desk surround background (#RRGGBB).
@@ -4344,14 +4336,16 @@ pub enum Message {
DsColorMore(DsField),
/// Open the shared CAD colour picker for a field and its current colour.
OpenColorWindow(ColorPickTarget, AcadColor),
/// Switch between Index Color and True Color.
ColorPickerTabChanged(ColorPickerTab),
/// Change the pending colour without committing it yet.
ColorPickerColorChanged(AcadColor),
/// Interaction inside the Select Color panel.
ColorPicker(crate::ui::color_select::ColorPickerMsg),
/// Close the colour picker without choosing.
CloseColorPicker,
/// Commit the colour chosen in the shared picker.
ColorWindowPick(codec::types::Color),
/// Commit the colour chosen in the shared picker, with its `BOOK$NAME`
/// when it comes from a colour book.
ColorWindowPick(codec::types::Color, Option<String>),
/// An `.acb` file was read (and, on desktop, copied into the first
/// colour book folder).
ColorBookLoaded(Result<crate::ui::color_select::book::ColorBook, String>),
/// Set a block/linetype Handle field on the selected dim style from a
/// dropdown of available block-records / linetypes (by name).
DsSetHandle {
@@ -4756,7 +4750,6 @@ impl OpenCADStudio {
sheet_set: Default::default(),
sheet_set_pending_layout: None,
sheet_set_import_parent: None,
bg_picker: None,
block_palette: Default::default(),
xref_manager: Default::default(),
dock: Default::default(),
@@ -4805,9 +4798,9 @@ impl OpenCADStudio {
web_autosave_consumed: None,
#[cfg(target_arch = "wasm32")]
web_autosave_clear_after: Vec::new(),
color_pick_target: None,
color_picker_tab: ColorPickerTab::Index,
recent_colors: Vec::new(),
color_picker: None,
color_books: Default::default(),
color_books_read: false,
active_modal: None,
hyperlink_editor_handles: Vec::new(),
hyperlink_editor_url: String::new(),
+5 -1
View File
@@ -3160,7 +3160,11 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
}
if !preserve_entity_style {
crate::scene::view::dispatch::apply_color(&mut entity, self.ribbon.active_color);
crate::scene::view::dispatch::apply_named_color(
&mut entity,
self.ribbon.active_color,
self.ribbon.active_color_name(),
);
crate::scene::view::dispatch::apply_common_prop(
&mut entity,
"linetype",
+267 -102
View File
@@ -436,6 +436,41 @@ impl OpenCADStudio {
let tab = &self.tabs[self.active_tab];
(started, tab.id, tab.scene.geometry_epoch)
});
// The Select Color panel can open on top of another dialog, so it
// takes the keyboard first: Esc cancels, Enter confirms, the arrows
// walk the index palette, and every other keystroke is swallowed.
if self.color_picker.is_some() {
use crate::ui::color_select::ColorPickerMsg;
match &msg {
Message::CommandEscape => {
return self.update(Message::ColorPicker(ColorPickerMsg::Cancel));
}
Message::CommandFinalize => {
return self.update(Message::ColorPicker(ColorPickerMsg::Ok));
}
Message::ShortcutPressed(key) => match key.rsplit('+').next() {
Some("ESCAPE") => {
return self.update(Message::ColorPicker(ColorPickerMsg::Cancel));
}
Some("ENTER" | "RETURN") => {
return self.update(Message::ColorPicker(ColorPickerMsg::Ok));
}
Some("TAB") => {
return crate::ui::color_select::picker::focus_step(
!key.contains("SHIFT+"),
);
}
_ => {}
},
Message::ArrowKeyPressed { direction, .. } => {
return self.update(Message::ColorPicker(ColorPickerMsg::Arrow(*direction)));
}
_ => {}
}
if is_modal_blocked_key_msg(&msg) {
return Task::none();
}
}
// A modal dialog must capture the keyboard the same way it already
// captures the mouse. Otherwise keystrokes from the global key
// subscription leak past the modal into the command line and fire as
@@ -4330,38 +4365,7 @@ impl OpenCADStudio {
Task::none()
}
Message::LayerColorSet(color) => {
let i = self.active_tab;
// Apply to every selected layer (multi-select), not just one.
let names = self.selected_layer_names(i);
if !names.is_empty() {
let undo = self.begin_layer_undo(i, "LAYER COLOR", &names);
for name in &names {
if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(name) {
dl.color = color;
dl.color_name = None;
dl.book_name = None;
}
}
for pl in self.tabs[i].layers.layers.iter_mut() {
if names.contains(&pl.name) {
pl.color = color;
pl.color_name = None;
pl.book_name = None;
}
}
self.tabs[i].dirty = true;
self.commit_layer_undo(i, undo);
// ByLayer color is baked into the cached wires at
// tessellation time, so bump the geometry epoch to
// invalidate the wire cache and repaint with the new color.
self.tabs[i].scene.invalidate_layer_dependencies(&names);
self.tabs[i].layers.color_picker_row = None;
self.tabs[i].layers.color_full_palette = false;
self.sync_ribbon_layers();
}
Task::none()
}
Message::LayerColorSet(color) => self.set_layer_color(color, None),
Message::LayerLinetypeSet(lt) => {
let i = self.active_tab;
@@ -4473,7 +4477,7 @@ impl OpenCADStudio {
Message::PaneClicked(pane) => self.on_pane_clicked(pane),
Message::PaneDragged(ev) => self.on_pane_dragged(ev),
Message::PaneMove(idx, local) => {
if self.color_pick_target.is_some() {
if self.color_picker.is_some() {
return Task::none();
}
let p = self.pane_canvas_point(idx, local);
@@ -7432,22 +7436,7 @@ impl OpenCADStudio {
Task::none()
}
Message::PropColorChanged(color) => {
let i = self.active_tab;
let handles = self.property_target_handles(i);
if handles.is_empty() {
let task = self.on_ribbon_color_changed(color);
self.refresh_properties();
return task;
}
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
if let Some(entity) = app.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::view::dispatch::apply_color(entity, color);
}
});
self.tabs[i].properties.color_picker_open = false;
Task::none()
}
Message::PropColorChanged(color) => self.set_property_color(color, None),
Message::PropLwChanged(lw) => {
let i = self.active_tab;
@@ -8958,39 +8947,6 @@ impl OpenCADStudio {
Task::none()
}
Message::BgPickerOpen(target) => {
self.bg_picker = Some(target);
Task::none()
}
Message::BgPickerCancel => {
self.bg_picker = None;
Task::none()
}
Message::BgPickerSubmit(color) => {
// Hand the result to the same handler the typed hex field
// uses, so the wheel and the field cannot drift apart on
// validation, persistence or the MatchTheme fallback.
let hex = crate::app::config::rgb_to_hex([
(color.r * 255.0).round() as u8,
(color.g * 255.0).round() as u8,
(color.b * 255.0).round() as u8,
]);
match self.bg_picker.take() {
Some(crate::app::BgTarget::Model) => {
Task::done(Message::ModelSpaceBgChanged(hex))
}
Some(crate::app::BgTarget::Paper) => {
Task::done(Message::PaperSpaceBgChanged(hex))
}
Some(crate::app::BgTarget::Desk) => {
Task::done(Message::DeskSpaceBgChanged(hex))
}
None => Task::none(),
}
}
Message::ModelSpaceBgChanged(hex) => {
self.model_bg_input = hex.clone();
if hex.trim().is_empty() {
@@ -11922,10 +11878,21 @@ impl OpenCADStudio {
) {
self.graphic_attributes.open_menu = None;
}
self.color_pick_target = Some((target, color));
// Always open the shared CAD colour picker on the indexed ACI page.
self.color_picker_tab = crate::app::ColorPickerTab::Index;
let (current, varies, name) = self.color_picker_current(&target, color);
let mut picker = crate::ui::color_select::ColorPicker::new(target, current, varies);
if !matches!(picker.target, crate::app::ColorPickTarget::Interface(_)) {
self.read_color_books();
let by_layer = self.color_picker_layer_color(&picker.target);
let tab = &self.tabs[self.active_tab];
picker = picker.with_drawing(
crate::ui::color_select::document::collect(&tab.scene.document),
&tab.recent_colors,
std::sync::Arc::clone(&self.color_books),
name,
by_layer,
);
}
self.color_picker = Some(picker);
self.modal_offset = iced::Vector::ZERO;
self.ds_color_open = None;
self.mls_color_open = None;
@@ -11940,29 +11907,226 @@ impl OpenCADStudio {
Task::none()
}
Message::ColorPickerTabChanged(tab) => {
self.color_picker_tab = tab;
Task::none()
}
Message::ColorPickerColorChanged(color) => {
if let Some((_, current)) = self.color_pick_target.as_mut() {
*current = color;
Message::ColorPicker(msg) => {
let Some(picker) = self.color_picker.as_mut() else {
return Task::none();
};
match picker.update(msg) {
Some(crate::ui::color_select::Outcome::Commit(color, name)) => {
self.update(Message::ColorWindowPick(color, name))
}
Some(crate::ui::color_select::Outcome::LoadBook) => Task::perform(
crate::ui::color_select::book::load_from_dialog(),
Message::ColorBookLoaded,
),
Some(crate::ui::color_select::Outcome::Cancel) => {
self.color_picker = None;
Task::none()
}
Some(crate::ui::color_select::Outcome::Screenshot) => {
use crate::ui::color_select::{eyedropper::EyedropperMsg, ColorPickerMsg};
match self.main_window {
Some(id) => iced::window::screenshot(id).map(|shot| {
Message::ColorPicker(ColorPickerMsg::Eyedropper(
EyedropperMsg::Shot(shot),
))
}),
// Nothing to capture: leave the eyedropper instead
// of waiting for a screenshot that never comes.
None => self.update(Message::ColorPicker(ColorPickerMsg::Cancel)),
}
}
None => Task::none(),
}
Task::none()
}
Message::CloseColorPicker => {
self.color_pick_target = None;
self.color_picker = None;
Task::none()
}
Message::ColorWindowPick(color) => {
self.note_recent_color(color);
self.on_color_window_pick(color)
Message::ColorWindowPick(color, name) => {
// Interface colours are not drawing colours: keep them out of
// the recent list.
if !matches!(
self.color_picker.as_ref().map(|picker| &picker.target),
Some(crate::app::ColorPickTarget::Interface(_))
) {
self.note_recent_color(color);
}
self.on_color_window_pick(color, name)
}
Message::ColorBookLoaded(Ok(book)) => {
// A book loaded again replaces the earlier copy.
let index = crate::ui::color_select::book::add_or_replace(
std::sync::Arc::make_mut(&mut self.color_books),
book,
);
if let Some(picker) = self.color_picker.as_mut() {
picker.show_book(std::sync::Arc::clone(&self.color_books), index);
}
Task::none()
}
Message::ColorBookLoaded(Err(error)) => {
if let Some(picker) = self.color_picker.as_mut().filter(|_| error != "Cancelled") {
picker.set_status(error);
}
Task::none()
}
Message::DsSetHandle { field, value } => self.on_ds_set_handle(field, value),
}
}
/// The colour the Select Color panel shows as "Current", and whether the
/// selection it edits has mixed colours. A mixed selection starts from the
/// colour of its most recently drawn object (the highest handle).
/// Also returns the colour-book identity (`BOOK$NAME`) of that colour.
fn color_picker_current(
&self,
target: &crate::app::ColorPickTarget,
color: codec::types::Color,
) -> (codec::types::Color, bool, Option<String>) {
let i = self.active_tab;
let document = &self.tabs[i].scene.document;
let name_of = |handle| {
document
.get_entity(handle)
.and_then(|entity| entity.common().color_name.clone())
};
match target {
crate::app::ColorPickTarget::Properties => {}
crate::app::ColorPickTarget::Ribbon => {
return (color, false, self.ribbon.active_color_name());
}
crate::app::ColorPickTarget::Layer(layer) => {
let name = document
.layers
.get(layer)
.and_then(crate::ui::color_select::document::layer_identity);
return (color, false, name);
}
_ => return (color, false, None),
}
let mut colors = self
.property_target_handles(i)
.into_iter()
.filter_map(|handle| {
document
.get_entity(handle)
.map(|entity| (handle, entity.common().color))
})
.collect::<Vec<_>>();
let Some(&(first_handle, first)) = colors.first() else {
return (color, false, None);
};
if colors.iter().all(|&(_, c)| c == first) {
return (first, false, name_of(first_handle));
}
colors.sort_by_key(|&(handle, _)| handle);
let (newest_handle, newest) = colors.last().copied().unwrap_or((first_handle, color));
(newest, true, name_of(newest_handle))
}
/// The colour ByLayer stands for in the Select Color panel: the layer of
/// the newest selected object, or the current layer for new objects.
fn color_picker_layer_color(
&self,
target: &crate::app::ColorPickTarget,
) -> Option<codec::types::Color> {
let i = self.active_tab;
let document = &self.tabs[i].scene.document;
let layer = match target {
crate::app::ColorPickTarget::Properties => self
.property_target_handles(i)
.into_iter()
.max()
.and_then(|handle| document.get_entity(handle))
.map_or(self.tabs[i].active_layer.as_str(), |entity| {
entity.as_entity().layer()
}),
crate::app::ColorPickTarget::Ribbon => self.tabs[i].active_layer.as_str(),
_ => return None,
};
document.layers.get(layer).map(|layer| layer.color)
}
/// Read the built-in colour book and every `.acb` in the colour book
/// folder, once per session. A book in the folder replaces a built-in
/// one of the same name.
fn read_color_books(&mut self) {
if std::mem::replace(&mut self.color_books_read, true) {
return;
}
self.color_books = std::sync::Arc::new(crate::ui::color_select::book::read_all());
}
/// Colour the selected objects (or set the current colour when nothing
/// is selected). `name` is a colour-book identity, `BOOK$NAME`.
pub(super) fn set_property_color(
&mut self,
color: codec::types::Color,
name: Option<String>,
) -> Task<Message> {
let i = self.active_tab;
let handles = self.property_target_handles(i);
if handles.is_empty() {
let task = self.on_ribbon_color_changed(color);
self.ribbon.active_color_book = name.map(|name| (color, name));
self.refresh_properties();
return task;
}
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
if let Some(entity) = app.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::view::dispatch::apply_named_color(entity, color, name.clone());
}
});
self.tabs[i].properties.color_picker_open = false;
Task::none()
}
/// Colour every selected layer (multi-select), not just one. `name` is a
/// colour-book identity, `BOOK$NAME`.
pub(super) fn set_layer_color(
&mut self,
color: codec::types::Color,
name: Option<&str>,
) -> Task<Message> {
let i = self.active_tab;
// Layers keep the book and the colour name apart.
let (book_name, color_name) = match name.map(|name| name.split_once('$')) {
Some(Some((book, name))) => (Some(book), Some(name)),
Some(None) => (None, name),
None => (None, None),
};
let names = self.selected_layer_names(i);
if !names.is_empty() {
let undo = self.begin_layer_undo(i, "LAYER COLOR", &names);
for name in &names {
if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(name) {
dl.color = color;
dl.color_name = color_name.map(str::to_string);
dl.book_name = book_name.map(str::to_string);
}
}
for pl in self.tabs[i].layers.layers.iter_mut() {
if names.contains(&pl.name) {
pl.color = color;
pl.color_name = color_name.map(str::to_string);
pl.book_name = book_name.map(str::to_string);
}
}
self.tabs[i].dirty = true;
self.commit_layer_undo(i, undo);
// ByLayer color is baked into the cached wires at
// tessellation time, so bump the geometry epoch to
// invalidate the wire cache and repaint with the new color.
self.tabs[i].scene.invalidate_layer_dependencies(&names);
self.tabs[i].layers.color_picker_row = None;
self.tabs[i].layers.color_full_palette = false;
self.sync_ribbon_layers();
}
Task::none()
}
pub(crate) fn note_recent_color(&mut self, color: codec::types::Color) {
// Keep only real colours in the recent list. ByLayer / ByBlock / None
// are logical CAD states rather than reusable colours.
@@ -11971,12 +12135,13 @@ impl OpenCADStudio {
codec::types::Color::Index(_) | codec::types::Color::Rgb { .. }
) {
// No duplicates: selecting an existing colour moves it to the front.
if let Some(pos) = self.recent_colors.iter().position(|c| c == &color) {
self.recent_colors.remove(pos);
let recent = &mut self.tabs[self.active_tab].recent_colors;
if let Some(pos) = recent.iter().position(|c| c == &color) {
recent.remove(pos);
}
self.recent_colors.insert(0, color);
self.recent_colors.truncate(12);
recent.insert(0, color);
recent.truncate(12);
}
}
+54 -27
View File
@@ -1136,42 +1136,60 @@ impl OpenCADStudio {
Task::none()
}
pub(super) fn on_color_window_pick(&mut self, color: codec::types::Color) -> Task<Message> {
pub(super) fn on_color_window_pick(
&mut self,
color: codec::types::Color,
name: Option<String>,
) -> Task<Message> {
if let Some(crate::app::ColorPickTarget::Interface(target)) = self
.color_picker
.as_ref()
.map(|picker| picker.target.clone())
{
self.color_picker = None;
let Some((r, g, b)) = crate::ui::color_select::math::color_rgb(color) else {
return Task::none();
};
// Hand the result to the same handler the typed hex field uses, so
// the picker and the field cannot drift apart on validation or
// persistence.
let hex = crate::app::config::rgb_to_hex([r, g, b]);
use crate::app::UiColor;
return Task::done(match target {
UiColor::ModelBackground => Message::ModelSpaceBgChanged(hex),
UiColor::PaperBackground => Message::PaperSpaceBgChanged(hex),
UiColor::DeskBackground => Message::DeskSpaceBgChanged(hex),
UiColor::Theme(index) => Message::OptionsThemeColorChanged(index, hex),
UiColor::Crosshair => Message::CrosshairColorChanged(hex),
UiColor::SnapMarker => Message::SnapMarkerColorChanged(hex),
UiColor::CommandText => Message::CommandTextColorChanged(hex),
});
}
if matches!(
self.color_pick_target.as_ref().map(|(target, _)| target),
self.color_picker.as_ref().map(|picker| &picker.target),
Some(crate::app::ColorPickTarget::PlotStyle)
) {
self.color_pick_target = None;
self.color_picker = None;
let rgb = match color {
codec::types::Color::Rgb { r, g, b } => Some((r, g, b)),
codec::types::Color::Index(index) => codec::types::aci_table::aci_to_rgb(index),
_ => None,
};
if let Some((r, g, b)) = rgb {
self.ps_color_buf = format!("#{r:02X}{g:02X}{b:02X}");
if let Some((r, g, b)) = crate::ui::color_select::math::color_rgb(color) {
self.ps_color_buf = crate::ui::color_select::math::hex_string(r, g, b);
}
return self.on_plot_style_panel_apply();
}
let s = crate::ui::color_select::color_to_aci_string(color);
let edit = match self.color_pick_target.take().map(|(target, _)| target) {
let edit = match self.color_picker.take().map(|picker| picker.target) {
Some(crate::app::ColorPickTarget::Gradient(index)) => {
Some(Message::GraphicAttributes(GraphicAttributesMsg::Gradient(
crate::ui::window::gradient_editor::GradientMsg::Color(index, color),
)))
}
Some(crate::app::ColorPickTarget::ViewBackground(slot)) => {
let rgb = match color {
codec::types::Color::Rgb { r, g, b } => Some([r, g, b]),
codec::types::Color::Index(index) => {
codec::types::aci_table::aci_to_rgb(index).map(|(r, g, b)| [r, g, b])
}
_ => None,
};
rgb.map(|rgb| {
Message::PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg::BgColor(slot, rgb))
crate::ui::color_select::math::color_rgb(color).map(|(r, g, b)| {
Message::PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg::BgColor(
slot,
[r, g, b],
))
})
}
Some(crate::app::ColorPickTarget::GraphicAttributesLine) => Some(
@@ -1189,7 +1207,9 @@ impl OpenCADStudio {
field: f,
value: s,
}),
Some(crate::app::ColorPickTarget::Properties) => Some(Message::PropColorChanged(color)),
Some(crate::app::ColorPickTarget::Properties) => {
return self.set_property_color(color, name);
}
Some(crate::app::ColorPickTarget::PropertiesBg) => {
Some(Message::PropBgColorChanged(color))
}
@@ -1197,13 +1217,17 @@ impl OpenCADStudio {
Some(Message::PropColorFieldChanged { field, color })
}
Some(crate::app::ColorPickTarget::MText) => Some(Message::MTextColorChanged(color)),
Some(crate::app::ColorPickTarget::Ribbon) => Some(Message::RibbonColorChanged(color)),
Some(crate::app::ColorPickTarget::Layer(name)) => {
Some(crate::app::ColorPickTarget::Ribbon) => {
let task = self.update(Message::RibbonColorChanged(color));
self.ribbon.active_color_book = name.map(|name| (color, name));
return task;
}
Some(crate::app::ColorPickTarget::Layer(layer)) => {
let i = self.active_tab;
match self.layer_panel_index(i, &name) {
match self.layer_panel_index(i, &layer) {
Some(idx) => {
self.tabs[i].layers.selected = Some(idx);
Some(Message::LayerColorSet(color))
return self.set_layer_color(color, name.as_deref());
}
None => None,
}
@@ -1211,7 +1235,10 @@ impl OpenCADStudio {
Some(crate::app::ColorPickTarget::LayerState(idx)) => {
Some(Message::LayerStateEditorLayerColor(idx, color))
}
Some(crate::app::ColorPickTarget::PlotStyle) => None,
// Handled before this match.
Some(
crate::app::ColorPickTarget::PlotStyle | crate::app::ColorPickTarget::Interface(_),
) => None,
None => None,
};
if let Some(m) = edit {
+2 -2
View File
@@ -1144,7 +1144,7 @@ impl OpenCADStudio {
p: Point,
expected_viewport: Option<codec::Handle>,
) -> Task<Message> {
if self.color_pick_target.is_some() {
if self.color_picker.is_some() {
return Task::none();
}
@@ -1232,7 +1232,7 @@ impl OpenCADStudio {
// leaks through the stack to the pane mouse_area beneath and
// would track the crosshair over the dropdown's empty areas.
// Drop the move here instead. (#227)
if self.ribbon.open_dropdown.is_some() || self.color_pick_target.is_some() {
if self.ribbon.open_dropdown.is_some() || self.color_picker.is_some() {
return Task::none();
}
let i = self.active_tab;
+14 -36
View File
@@ -2303,7 +2303,7 @@ bg={bg_ms:.1}ms n={view_count}"
&redo_labels,
self.win_size,
self.tabs[self.active_tab].is_start,
&self.recent_colors,
&self.tabs[self.active_tab].recent_colors,
)
.unwrap_or_else(|| iced::widget::Space::new().width(0).height(0).into())
};
@@ -2431,42 +2431,20 @@ bg={bg_ms:.1}ms n={view_count}"
}
}
if let Some((_, current)) = self.color_pick_target.as_ref() {
match self.color_picker_tab {
super::ColorPickerTab::Index => {
let content =
crate::ui::color_select::index_color_page(*current, &self.recent_colors);
crate::ui::modal::modal(
base,
t!("Select Color"),
content,
Message::CloseColorPicker,
self.modal_offset,
crate::ui::modal::ModalOptions::NOTICE,
)
}
super::ColorPickerTab::TrueColor => {
let initial = crate::ui::properties::acad_color_display(*current).0;
let modal_base = crate::ui::modal::backdrop(base, Message::CloseColorPicker);
iced_aw::ColorPicker::new(
true,
initial,
modal_base,
// Cancel inside True Color returns to the indexed page instead
// of closing the whole CAD colour picker.
Message::ColorPickerTabChanged(super::ColorPickerTab::Index),
|color| {
Message::ColorWindowPick(crate::ui::color_select::iced_to_acad_color(
color,
))
},
)
.into()
}
if let Some(picker) = self.color_picker.as_ref() {
// While the eyedropper is picking, the panel steps aside so the
// whole window can be sampled.
if let Some(eyedropper) = picker.eyedropper_view() {
return iced::widget::stack![base, eyedropper].into();
}
crate::ui::modal::modal(
base,
t!("Select Color"),
picker.view(),
Message::CloseColorPicker,
self.modal_offset,
crate::ui::modal::ModalOptions::NOTICE,
)
} else {
base
}
+2 -1
View File
@@ -373,6 +373,8 @@ impl OpenCADStudio {
.map(|_| std::env::temp_dir().display().to_string()),
fonts: crate::io::font_repo::fonts_dir()
.map(|p| p.display().to_string()),
color_books: crate::ui::color_select::book::folder()
.map(|p| p.display().to_string()),
}
}
#[cfg(target_arch = "wasm32")]
@@ -395,7 +397,6 @@ impl OpenCADStudio {
&self.model_bg_input,
&self.paper_bg_input,
&self.desk_bg_input,
self.bg_picker,
dirty,
close_confirm,
flow,
+20 -4
View File
@@ -4931,15 +4931,31 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"Dismiss" => Some(("common", "dismiss")),
"?" => Some(("common", "value-8a8de8")),
"Select Color..." => Some(("common", "select-color-8dddf1")),
"No recent colors yet" => Some(("common", "no-recent-colors-yet")),
"True Color RGB {r}, {g}, {b}" => Some(("common", "true-color-rgb")),
"Index Color" => Some(("common", "index-color")),
"True Color" => Some(("common", "true-color")),
"Standard colors" => Some(("common", "standard-colors")),
"Hex" => Some(("common", "color-hex")),
"Nearest index" => Some(("common", "nearest-index-color")),
"Exact index match" => Some(("common", "exact-index-match")),
"Recently used" => Some(("common", "color-recently-used")),
"Most used" => Some(("common", "color-most-used")),
"Color number" => Some(("common", "color-number")),
"Name A–Z" => Some(("common", "color-name-az")),
"Name Z–A" => Some(("common", "color-name-za")),
"Sort" => Some(("common", "color-sort")),
"No colors in this drawing yet" => Some(("common", "color-no-drawing-colors")),
"Color Books" => Some(("common", "color-books")),
"No color books loaded" => Some(("common", "color-no-books")),
"Load color book (.acb)" => Some(("common", "color-load-book")),
"Color book" => Some(("common", "color-book-label")),
"RGB equivalent" => Some(("common", "color-rgb-equivalent")),
"No colors match" => Some(("common", "color-no-match")),
"Not a color book" => Some(("common", "color-not-a-book")),
"This color book is encrypted and cannot be read" => Some(("common", "color-book-encrypted")),
"The color book has no colors" => Some(("common", "color-book-empty")),
"Color book locations" => Some(("common", "color-book-locations")),
"CAD Color Index (ACI) 10–249" => Some(("common", "cad-color-index-aci-10-249")),
"Color family → · Shade / intensity ↓" => Some(("common", "color-family-shade-intensity")),
"Grayscale 250–255" => Some(("common", "grayscale-250-255")),
"Recently used" => Some(("common", "recently-used")),
"Isometric drafting" => Some(("common", "isometric-drafting")),
"F5 cycles the active plane." => Some(("common", "f5-cycles-the-active-plane")),
"Units…" => Some(("common", "units-5430a1")),
+6
View File
@@ -322,6 +322,12 @@ pub fn apply_color(entity: &mut EntityType, color: AcadColor) {
common.color_book_handle = None;
}
/// Like [`apply_color`], but keeps a colour-book identity (`BOOK$NAME`).
pub fn apply_named_color(entity: &mut EntityType, color: AcadColor, name: Option<String>) {
apply_color(entity, color);
entity.common_mut().color_name = name;
}
pub fn apply_line_weight(entity: &mut EntityType, lw: LineWeight) {
entity.as_entity_mut().set_line_weight(lw);
}
+251
View File
@@ -0,0 +1,251 @@
//! Colour books: AutoCAD `.acb` files.
//!
//! An `.acb` is XML: a `bookName` and `colorPage`s of `colorEntry`s, each a
//! `colorName` and an `RGB8` value. Books from some vendors encrypt their
//! values; those cannot be read.
/// One named colour.
#[derive(Debug, Clone, PartialEq)]
pub struct BookColor {
pub name: String,
pub rgb: (u8, u8, u8),
}
#[derive(Debug, Clone, PartialEq)]
pub struct ColorBook {
pub name: String,
/// Every colour, page after page.
pub colors: Vec<BookColor>,
/// Where each page starts in `colors`.
page_starts: Vec<usize>,
}
/// Pages for a book without `colorPage`s, as AutoCAD shows them.
const PAGE_SIZE: usize = 7;
impl ColorBook {
pub fn page_count(&self) -> usize {
self.page_starts.len()
}
/// The colours of one page, and the index of its first colour.
pub fn page(&self, page: usize) -> (usize, &[BookColor]) {
let start = self.page_starts.get(page).copied().unwrap_or(0);
let end = self
.page_starts
.get(page + 1)
.copied()
.unwrap_or(self.colors.len());
(start, &self.colors[start..end])
}
/// The page holding colour `index`.
pub fn page_of(&self, index: usize) -> usize {
self.page_starts
.partition_point(|&start| start <= index)
.saturating_sub(1)
}
/// The colour with this name, as stored after the `$` of `BOOK$NAME`.
pub fn find(&self, name: &str) -> Option<usize> {
self.colors.iter().position(|c| c.name == name)
}
/// The `BOOK$NAME` identity objects and layers store.
pub fn identity(&self, index: usize) -> String {
format!("{}${}", self.name, self.colors[index].name)
}
}
/// Add `book`, replacing one of the same name; returns where it now is.
pub fn add_or_replace(books: &mut Vec<ColorBook>, book: ColorBook) -> usize {
match books.iter().position(|b| b.name == book.name) {
Some(index) => {
books[index] = book;
index
}
None => {
books.push(book);
books.len() - 1
}
}
}
/// The colour book that ships with the program; its licence is in the
/// file's header comment.
pub fn builtin() -> impl Iterator<Item = ColorBook> {
parse(include_str!("../../../assets/color_books/ral-classic.acb")).into_iter()
}
/// Where colour books are kept: `color_books` in the config folder. The
/// web has no folders.
#[cfg(not(target_arch = "wasm32"))]
pub fn folder() -> Option<std::path::PathBuf> {
crate::config::config_dir().map(|dir| dir.join("color_books"))
}
/// The built-in book plus every `.acb` file in the colour book folder, in
/// file-name order. A book replaces an earlier one of the same name.
pub fn read_all() -> Vec<ColorBook> {
#[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
let mut books: Vec<_> = builtin().collect();
#[cfg(not(target_arch = "wasm32"))]
if let Some(dir) = folder() {
let mut files: Vec<_> = std::fs::read_dir(dir)
.into_iter()
.flatten()
.filter_map(|entry| entry.ok().map(|entry| entry.path()))
.filter(|path| {
path.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("acb"))
})
.collect();
files.sort();
for book in files
.into_iter()
.filter_map(|path| std::fs::read_to_string(path).ok())
.filter_map(|text| parse(&text).ok())
{
add_or_replace(&mut books, book);
}
}
books
}
/// Ask for an `.acb` file and read it. On desktop the file is copied into
/// the colour book folder, so it stays when the original moves; the web
/// keeps the book for the session only.
pub async fn load_from_dialog() -> Result<ColorBook, String> {
let file = crate::sys::file_dialog()
.add_filter(crate::t!("Color book").as_ref(), &["acb", "ACB"])
.pick_file()
.await
.ok_or_else(|| "Cancelled".to_string())?;
let bytes = file.read().await;
let book = parse(&String::from_utf8_lossy(&bytes))?;
#[cfg(not(target_arch = "wasm32"))]
if let Some(folder) = folder() {
std::fs::create_dir_all(&folder)
.and_then(|()| std::fs::write(folder.join(file.file_name()), &bytes))
.map_err(|e| e.to_string())?;
}
Ok(book)
}
/// Read an `.acb` file's text.
pub fn parse(text: &str) -> Result<ColorBook, String> {
let doc = roxmltree::Document::parse(text.trim_start_matches('\u{feff}'))
.map_err(|e| e.to_string())?;
let root = doc.root_element();
if !root.has_tag_name("colorBook") {
return Err(crate::t!("Not a color book").into_owned());
}
let mut book = ColorBook {
name: text_of(root, "bookName").unwrap_or_default().to_string(),
colors: Vec::new(),
page_starts: Vec::new(),
};
let mut encrypted = false;
let mut read_entries = |parent: roxmltree::Node<'_, '_>, book: &mut ColorBook| {
for entry in parent.children().filter(|n| n.has_tag_name("colorEntry")) {
let rgb = child(entry, "RGB8").and_then(|rgb| {
let channel = |tag| text_of(rgb, tag)?.parse::<u8>().ok();
Some((channel("red")?, channel("green")?, channel("blue")?))
});
match (text_of(entry, "colorName"), rgb) {
(Some(name), Some(rgb)) => book.colors.push(BookColor {
name: name.to_string(),
rgb,
}),
_ => encrypted = true,
}
}
};
let mut pages = root
.children()
.filter(|n| n.has_tag_name("colorPage"))
.peekable();
if pages.peek().is_some() {
for page in pages {
let start = book.colors.len();
read_entries(page, &mut book);
if book.colors.len() > start {
book.page_starts.push(start);
}
}
} else {
read_entries(root, &mut book);
book.page_starts = (0..book.colors.len()).step_by(PAGE_SIZE).collect();
}
if book.colors.is_empty() {
return Err(if encrypted {
crate::t!("This color book is encrypted and cannot be read")
} else {
crate::t!("The color book has no colors")
}
.into_owned());
}
Ok(book)
}
fn child<'a, 'i>(node: roxmltree::Node<'a, 'i>, tag: &str) -> Option<roxmltree::Node<'a, 'i>> {
node.children().find(|n| n.has_tag_name(tag))
}
fn text_of<'a>(node: roxmltree::Node<'a, '_>, tag: &str) -> Option<&'a str> {
child(node, tag).and_then(|n| n.text()).map(str::trim)
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(name: &str, (r, g, b): (u8, u8, u8)) -> String {
format!(
"<colorEntry><colorName>{name}</colorName>\
<RGB8><red>{r}</red><green>{g}</green><blue>{b}</blue></RGB8></colorEntry>"
)
}
#[test]
fn reads_pages_and_colours() {
let text = format!(
"\u{feff}<?xml version=\"1.0\"?><colorBook><bookName>RAL CLASSIC</bookName>\
<colorPage><pageColor><RGB8><red>1</red><green>2</green><blue>3</blue></RGB8></pageColor>{}{}</colorPage>\
<colorPage>{}</colorPage></colorBook>",
entry("RAL 1000", (190, 189, 127)),
entry("RAL 1001", (194, 176, 120)),
entry("RAL 5015", (34, 113, 179)),
);
let book = parse(&text).unwrap();
assert_eq!(book.name, "RAL CLASSIC");
assert_eq!(book.page_count(), 2);
assert_eq!(book.page(1), (2, &book.colors[2..]));
assert_eq!(book.page_of(1), 0);
assert_eq!(book.page_of(2), 1);
let blue = book.find("RAL 5015").unwrap();
assert_eq!(book.colors[blue].rgb, (34, 113, 179));
assert_eq!(book.identity(blue), "RAL CLASSIC$RAL 5015");
}
#[test]
fn a_book_without_pages_gets_pages_of_seven() {
let entries: String = (0..9).map(|i| entry(&format!("C{i}"), (i, i, i))).collect();
let book = parse(&format!(
"<colorBook><bookName>B</bookName>{entries}</colorBook>"
))
.unwrap();
assert_eq!(book.page_count(), 2);
assert_eq!(book.page(1).1.len(), 2);
}
#[test]
fn rejects_unreadable_books() {
assert!(parse("not xml").is_err());
assert!(parse("<palette/>").is_err());
let encrypted = "<colorBook><bookName>P</bookName><colorPage><colorEntry>\
<colorName>X</colorName><RGB8Encrypt>AbC</RGB8Encrypt></colorEntry>\
</colorPage></colorBook>";
assert!(parse(encrypted).is_err());
}
}
+247
View File
@@ -0,0 +1,247 @@
//! The colours a drawing uses, for the Document tab.
//!
//! One pass over every object (block definitions included) and every layer
//! when the panel opens. Keys borrow from the drawing, so the pass allocates
//! only per distinct colour, never per object.
use super::math;
use codec::types::{Color as AcadColor, Handle};
use rustc_hash::FxHashMap;
/// One distinct colour: a concrete colour plus, for a colour-book colour,
/// its `BOOK$NAME` identity.
#[derive(Debug, Clone, PartialEq)]
pub struct DocColor {
pub color: AcadColor,
pub name: Option<String>,
/// Objects and layers using it. Only used for sorting, never shown.
uses: u32,
/// The newest object or layer using it.
newest: Handle,
}
impl DocColor {
/// The name in the list view: the colour-book colour's own name (without
/// the book), `ACI n` or `#RRGGBB`.
pub fn label(&self) -> String {
match &self.name {
Some(name) => name
.split_once('$')
.map_or(name.as_str(), |(_, name)| name)
.to_string(),
None => math::short_name(self.color),
}
}
}
/// Swatches or a list of named bars.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DocView {
#[default]
Grid,
List,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DocSort {
#[default]
Recent,
Usage,
Hue,
Number,
NameAsc,
NameDesc,
}
impl DocSort {
pub const ALL: [DocSort; 6] = [
DocSort::Recent,
DocSort::Usage,
DocSort::Hue,
DocSort::Number,
DocSort::NameAsc,
DocSort::NameDesc,
];
}
impl std::fmt::Display for DocSort {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&match self {
DocSort::Recent => crate::t!("Recently used"),
DocSort::Usage => crate::t!("Most used"),
DocSort::Hue => crate::t!("Color"),
DocSort::Number => crate::t!("Color number"),
DocSort::NameAsc => crate::t!("Name A–Z"),
DocSort::NameDesc => crate::t!("Name Z–A"),
})
}
}
/// A colour as a hash key: index colours keep their number, true colours
/// sit above 255.
fn key(color: AcadColor) -> Option<u32> {
match color {
AcadColor::Index(i) => Some(u32::from(i)),
AcadColor::Rgb { r, g, b } => {
Some(1 << 24 | u32::from(r) << 16 | u32::from(g) << 8 | u32::from(b))
}
_ => None,
}
}
/// A layer's colour-book identity, `BOOK$NAME`. Layers keep the book and
/// the colour name apart; objects store them joined like this.
pub fn layer_identity(layer: &codec::tables::Layer) -> Option<String> {
match (&layer.book_name, &layer.color_name) {
(Some(book), Some(name)) => Some(format!("{book}${name}")),
(None, Some(name)) => Some(name.clone()),
_ => None,
}
}
/// Every concrete colour in the drawing. ByLayer, ByBlock and None are
/// states rather than colours and are skipped.
pub fn collect(document: &codec::CadDocument) -> Vec<DocColor> {
let layer_names: Vec<Option<String>> = document.layers.iter().map(layer_identity).collect();
let objects = document.entities().map(|entity| {
let common = entity.common();
(common.color, common.color_name.as_deref(), common.handle)
});
let layers = document
.layers
.iter()
.zip(&layer_names)
.map(|(layer, name)| (layer.color, name.as_deref(), layer.handle));
tally(objects.chain(layers))
}
fn tally<'a>(uses: impl Iterator<Item = (AcadColor, Option<&'a str>, Handle)>) -> Vec<DocColor> {
let mut index: FxHashMap<(u32, Option<&str>), usize> = FxHashMap::default();
let mut colors: Vec<DocColor> = Vec::new();
for (color, name, handle) in uses {
let Some(k) = key(color) else { continue };
let name = name.filter(|name| !name.is_empty());
let slot = *index.entry((k, name)).or_insert_with(|| {
colors.push(DocColor {
color,
name: name.map(str::to_string),
uses: 0,
newest: handle,
});
colors.len() - 1
});
let entry = &mut colors[slot];
entry.uses += 1;
entry.newest = entry.newest.max(handle);
}
colors
}
/// Sort for display. `recent` is this drawing's pick history, newest first;
/// colours not picked this session follow, newest object first.
pub fn sort(colors: &mut [DocColor], by: DocSort, recent: &[AcadColor]) {
match by {
// The rank is looked up once per colour, not on every comparison.
DocSort::Recent => colors.sort_by_cached_key(|c| {
let rank = recent
.iter()
.position(|&r| r == c.color)
.unwrap_or(usize::MAX);
(rank, std::cmp::Reverse(c.newest))
}),
DocSort::Usage => colors.sort_by(|a, b| b.uses.cmp(&a.uses).then(b.newest.cmp(&a.newest))),
DocSort::Hue => colors.sort_by_cached_key(|c| {
let (r, g, b) = math::color_rgb(c.color).unwrap_or_default();
let hsb = math::rgb_to_hsb(r, g, b);
// Grays after the colours; within a hue band, light to dark.
let gray = hsb.s < 0.12 || hsb.b < 0.08;
let band = if gray {
0
} else {
(hsb.h / 15.0).round() as u32
};
(gray, band, std::cmp::Reverse((hsb.b * 1000.0) as u32))
}),
DocSort::Number => colors.sort_by(|a, b| {
let kind = |c: &DocColor| match (&c.name, c.color) {
(None, AcadColor::Index(_)) => 0,
(None, _) => 1,
_ => 2,
};
kind(a)
.cmp(&kind(b))
.then_with(|| a.name.cmp(&b.name))
.then_with(|| key(a.color).cmp(&key(b.color)))
}),
DocSort::NameAsc => colors.sort_by_cached_key(|c| c.label().to_lowercase()),
DocSort::NameDesc => {
colors.sort_by_cached_key(|c| std::cmp::Reverse(c.label().to_lowercase()))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const RED: AcadColor = AcadColor::Index(1);
const BLUE: AcadColor = AcadColor::Index(5);
const SKY: AcadColor = AcadColor::Rgb {
r: 57,
g: 191,
b: 252,
};
fn sample() -> Vec<DocColor> {
let h = Handle::new;
tally(
[
(RED, None, h(1)),
(RED, None, h(2)),
(RED, None, h(3)),
(AcadColor::ByLayer, None, h(4)),
(SKY, None, h(5)),
(SKY, Some("RAL CLASSIC$RAL 5015"), h(6)),
(BLUE, Some(""), h(7)),
]
.into_iter(),
)
}
#[test]
fn collects_each_concrete_colour_once() {
let colors = sample();
assert_eq!(colors.len(), 4, "ByLayer skipped, book colour kept apart");
assert_eq!(colors[0].uses, 3);
assert_eq!(colors[0].newest, Handle::new(3));
assert_eq!(colors[3].name, None, "an empty name is no name");
assert_eq!(colors[2].label(), "RAL 5015");
assert_eq!(colors[0].label(), "ACI 1");
assert_eq!(colors[1].label(), "#39BFFC");
}
#[test]
fn sorts_four_ways() {
let order = |by, recent: &[AcadColor]| {
let mut colors = sample();
sort(&mut colors, by, recent);
colors
.iter()
.map(|c| (c.color, c.name.is_some()))
.collect::<Vec<_>>()
};
// Picked this session first, then newest object first.
assert_eq!(
order(DocSort::Recent, &[RED]),
[(RED, false), (BLUE, false), (SKY, true), (SKY, false)]
);
assert_eq!(order(DocSort::Usage, &[])[0], (RED, false));
assert_eq!(
order(DocSort::Number, &[]),
[(RED, false), (BLUE, false), (SKY, false), (SKY, true)]
);
// Red (hue 0) before sky blue (hue ~199) before blue (hue 240).
assert_eq!(order(DocSort::Hue, &[])[0], (RED, false));
assert_eq!(order(DocSort::Hue, &[])[3], (BLUE, false));
}
}
+506
View File
@@ -0,0 +1,506 @@
//! The colour editor: RGB, HSB or CMYK sliders above a saturation/brightness
//! field with a hue strip, plus a hex box. Shared by the CAD Select Color
//! panel and the interface colour picker.
use super::math::{self, Hsb};
use super::widgets::ColorArea;
use super::ColorPickerMsg;
use crate::app::Message;
use crate::t;
use iced::widget::{
button, column, container, row, slider, stack, text, text_input, vertical_slider, Space,
};
use iced::{Background, Border, Color, Element, Gradient, Length, Radians, Theme};
const ROW_HEIGHT: f32 = 26.0;
const TRACK: f32 = 18.0;
const STRIP_WIDTH: f32 = 22.0;
const ROW_GAP: f32 = 6.0;
/// The field's height in the interface picker, which has no fixed height.
const FIELD_HEIGHT: f32 = 96.0;
const MODE_WIDTH: f32 = 48.0;
const MODE_HEIGHT: f32 = 24.0;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EditorMode {
#[default]
Rgb,
Hsb,
Cmyk,
}
#[derive(Debug, Clone)]
pub enum EditorMsg {
Mode(EditorMode),
Hue(f32),
/// Saturation in percent.
Saturation(f32),
/// Brightness in percent.
Brightness(f32),
/// Saturation and brightness from the field, both 0–1.
Field(f32, f32),
Red(u8),
Green(u8),
Blue(u8),
/// One CMYK channel (0 cyan … 3 black), in percent.
Cmyk(usize, f32),
HexInput(String),
HexSubmit,
}
pub struct ColorEditor {
mode: EditorMode,
/// Kept beside `rgb` so hue and saturation survive grays and black.
hsb: Hsb,
/// Kept too, so black keeps its inks and rounding doesn't move them.
cmyk: [f32; 4],
rgb: (u8, u8, u8),
hex_draft: Option<String>,
}
impl ColorEditor {
pub fn new(rgb: (u8, u8, u8)) -> Self {
let mut editor = Self {
mode: EditorMode::default(),
hsb: Hsb {
h: 0.0,
s: 0.0,
b: 1.0,
},
cmyk: [0.0; 4],
rgb,
hex_draft: None,
};
editor.set_rgb(rgb);
editor
}
pub fn rgb(&self) -> (u8, u8, u8) {
self.rgb
}
pub fn set_rgb(&mut self, (r, g, b): (u8, u8, u8)) {
let next = math::rgb_to_hsb(r, g, b);
// A gray has no hue and black no saturation; keep the previous ones
// so the sliders and field don't jump.
let gray = next.s == 0.0 || next.b == 0.0;
self.hsb = Hsb {
h: if gray { self.hsb.h } else { next.h },
s: if next.b == 0.0 { self.hsb.s } else { next.s },
b: next.b,
};
self.cmyk = math::rgb_to_cmyk(r, g, b);
self.rgb = (r, g, b);
self.hex_draft = None;
}
fn set_hsb(&mut self, hsb: Hsb) {
self.hsb = Hsb {
h: hsb.h.clamp(0.0, 360.0),
s: hsb.s.clamp(0.0, 1.0),
b: hsb.b.clamp(0.0, 1.0),
};
self.rgb = self.hsb.to_rgb();
self.cmyk = math::rgb_to_cmyk(self.rgb.0, self.rgb.1, self.rgb.2);
self.hex_draft = None;
}
fn set_cmyk(&mut self, channel: usize, value: f32) {
let mut cmyk = self.cmyk;
cmyk[channel.min(3)] = value.clamp(0.0, 100.0);
self.set_rgb(math::cmyk_to_rgb(cmyk));
self.cmyk = cmyk;
}
/// Apply a message; returns whether the colour changed.
pub fn update(&mut self, msg: EditorMsg) -> bool {
let before = self.rgb;
let (r, g, b) = self.rgb;
match msg {
EditorMsg::Mode(mode) => self.mode = mode,
EditorMsg::Hue(h) => self.set_hsb(Hsb { h, ..self.hsb }),
EditorMsg::Saturation(s) => self.set_hsb(Hsb {
s: s / 100.0,
..self.hsb
}),
EditorMsg::Brightness(v) => self.set_hsb(Hsb {
b: v / 100.0,
..self.hsb
}),
EditorMsg::Field(s, b) => self.set_hsb(Hsb { s, b, ..self.hsb }),
EditorMsg::Red(v) => self.set_rgb((v, g, b)),
EditorMsg::Green(v) => self.set_rgb((r, v, b)),
EditorMsg::Blue(v) => self.set_rgb((r, g, v)),
EditorMsg::Cmyk(channel, v) => self.set_cmyk(channel, v),
EditorMsg::HexInput(value) => {
// Six digits are unambiguous, so apply them while typing; the
// short #RGB form waits for Enter.
let complete = value.trim().trim_start_matches('#').len() == 6;
if let Some(rgb) = math::parse_hex(&value).filter(|_| complete) {
self.set_rgb(rgb);
}
self.hex_draft = Some(value);
}
EditorMsg::HexSubmit => {
if let Some(rgb) = self.hex_draft.take().and_then(|d| math::parse_hex(&d)) {
self.set_rgb(rgb);
}
}
}
self.rgb != before
}
/// `mode_end` sits at the far right of the mode buttons (the pipette);
/// `hex_end` at the far right of the hex row (the nearest index colour).
/// With `fill` the field takes all the height the sliders leave, so the
/// hex row stays at the bottom and CMYK's fourth slider only makes the
/// field a little lower.
pub fn view<'a>(
&'a self,
mode_end: Option<Element<'a, Message>>,
hex_end: Option<Element<'a, Message>>,
fill: bool,
) -> Element<'a, Message> {
let height = if fill {
Length::Fill
} else {
Length::Fixed(FIELD_HEIGHT)
};
column![
self.mode_row(mode_end),
self.slider_rows(),
self.field(height),
self.hex_row(hex_end),
]
.spacing(12)
.into()
}
/// RGB, HSB and CMYK, with `end` at the far right.
fn mode_row<'a>(&self, end: Option<Element<'a, Message>>) -> Element<'a, Message> {
let mode_button = |label: &'static str, mode: EditorMode| {
let active = self.mode == mode;
button(container(text(label).size(13)).center(Length::Fill))
.on_press(msg(EditorMsg::Mode(mode)))
.width(MODE_WIDTH)
.height(MODE_HEIGHT)
.padding(0)
.style(move |theme: &Theme, status| {
super::picker::toggle_style(theme, active, status)
})
};
let mut modes = row![
mode_button("RGB", EditorMode::Rgb),
mode_button("HSB", EditorMode::Hsb),
mode_button("CMYK", EditorMode::Cmyk),
Space::new().width(Length::Fill),
]
.spacing(6);
if let Some(end) = end {
modes = modes.push(end);
}
modes.into()
}
/// Gradient sliders: red, green and blue; hue, saturation and
/// brightness; or cyan, magenta, yellow and black.
fn slider_rows<'a>(&self) -> Element<'a, Message> {
let Hsb { h, s, b } = self.hsb;
let (r, g, bl) = self.rgb;
let rows = if self.mode == EditorMode::Cmyk {
let ink = |i: usize, letter| {
let at = |v| {
let mut cmyk = self.cmyk;
cmyk[i] = v;
rgb_color(math::cmyk_to_rgb(cmyk))
};
channel(
letter,
self.cmyk[i],
100.0,
Some('%'),
vec![(0.0, at(0.0)), (1.0, at(100.0))],
move |v| EditorMsg::Cmyk(i, v),
)
};
column![ink(0, "C"), ink(1, "M"), ink(2, "Y"), ink(3, "K")]
} else if self.mode == EditorMode::Rgb {
let ends = |lo: (u8, u8, u8), hi: (u8, u8, u8)| {
vec![(0.0, rgb_color(lo)), (1.0, rgb_color(hi))]
};
let byte = |f: fn(u8) -> EditorMsg| move |v: f32| f(v.round() as u8);
column![
channel(
"R",
f32::from(r),
255.0,
None,
ends((0, g, bl), (255, g, bl)),
byte(EditorMsg::Red)
),
channel(
"G",
f32::from(g),
255.0,
None,
ends((r, 0, bl), (r, 255, bl)),
byte(EditorMsg::Green)
),
channel(
"B",
f32::from(bl),
255.0,
None,
ends((r, g, 0), (r, g, 255)),
byte(EditorMsg::Blue)
),
]
} else {
column![
channel("H", h, 360.0, Some('°'), hue_stops(), EditorMsg::Hue),
channel(
"S",
s * 100.0,
100.0,
Some('%'),
vec![(0.0, hsb_color(h, 0.0, b)), (1.0, hsb_color(h, 1.0, b))],
EditorMsg::Saturation,
),
channel(
"B",
b * 100.0,
100.0,
Some('%'),
vec![(0.0, Color::BLACK), (1.0, hsb_color(h, s, 1.0))],
EditorMsg::Brightness,
),
]
};
rows.spacing(ROW_GAP).into()
}
/// The saturation/brightness field with its hue strip.
fn field<'a>(&self, height: Length) -> Element<'a, Message> {
let Hsb { h, s, b } = self.hsb;
let area = ColorArea::new(hsb_color(h, 1.0, 1.0), rgb_color(self.rgb), s, b, |s, b| {
msg(EditorMsg::Field(s, b))
})
.height(height);
// A vertical slider grows upwards; the strip runs red at the top.
let strip = stack![
container(Space::new())
.width(STRIP_WIDTH)
.height(height)
.style(|_theme: &Theme| gradient_box(std::f32::consts::PI, &hue_stops())),
vertical_slider(0.0..=360.0, 360.0 - h, |v| msg(EditorMsg::Hue(360.0 - v)))
.width(STRIP_WIDTH)
.height(height)
.style(handle_style),
];
row![area, strip].spacing(12).into()
}
/// The hex value with `end` (the nearest index colour) at the far right.
fn hex_row<'a>(&self, end: Option<Element<'a, Message>>) -> Element<'a, Message> {
let (r, g, b) = self.rgb;
let hex = self
.hex_draft
.clone()
.unwrap_or_else(|| math::hex_string(r, g, b));
let mut hex_row = row![
text(t!("Hex")).size(11).style(super::picker::muted_text),
text_input("", &hex)
.on_input(|value| msg(EditorMsg::HexInput(value)))
.on_submit(msg(EditorMsg::HexSubmit))
.size(12)
.font(iced::Font::MONOSPACE)
.padding([5, 6])
.width(Length::Fixed(74.0)),
Space::new().width(Length::Fill),
]
.spacing(8)
.align_y(iced::Center);
if let Some(end) = end {
hex_row = hex_row.push(end);
}
hex_row.into()
}
}
fn msg(editor: EditorMsg) -> Message {
Message::ColorPicker(ColorPickerMsg::Editor(editor))
}
/// One labelled channel: a gradient slider and its number box.
fn channel<'a>(
letter: &'static str,
value: f32,
max: f32,
unit: Option<char>,
stops: Vec<(f32, Color)>,
on_change: impl Fn(f32) -> EditorMsg + Copy + 'static,
) -> Element<'a, Message> {
// iced's slider splits its own rail at the handle, which would squeeze a
// gradient; draw the gradient underneath and keep the rail transparent.
let track = container(
container(Space::new())
.width(Length::Fill)
.height(TRACK)
.style(move |_theme: &Theme| gradient_box(std::f32::consts::FRAC_PI_2, &stops)),
)
.height(ROW_HEIGHT)
.center_y(ROW_HEIGHT);
row![
container(text(letter).size(12).font(iced::Font::MONOSPACE))
.width(12)
.align_x(iced::Center),
stack![
track,
slider(0.0..=max, value, move |v| msg(on_change(v)))
.height(ROW_HEIGHT)
.shift_step(10.0)
.style(handle_style),
],
number_box(value, max, unit, on_change),
]
.spacing(8)
.align_y(iced::Center)
.into()
}
/// A compact spin box with the unit drawn inside, after the digits.
fn number_box(
value: f32,
max: f32,
unit: Option<char>,
on_change: impl Fn(f32) -> EditorMsg + Copy + 'static,
) -> Element<'static, Message> {
let input =
iced_aw::widget::NumberInput::new(&(value.round() as u16), 0..=max as u16, move |v| {
msg(on_change(f32::from(v)))
})
.set_size(12.0)
.font(iced::Font::MONOSPACE)
.width(Length::Fixed(54.0))
// Padding under 5 would make NumberInput swap its arrows for + / −;
// the extra right padding keeps the digits clear of the arrows.
.padding(iced::Padding {
top: 5.0,
right: 18.0,
bottom: 5.0,
left: 2.0,
})
.align_x(iced::alignment::Horizontal::Right)
.style(
|theme: &Theme, _status| iced_aw::style::number_input::Style {
button_background: None,
icon_color: theme.palette().background.base.text.scale_alpha(0.75),
},
);
match unit {
Some(code_point) => input
.icon(text_input::Icon {
font: iced::Font::MONOSPACE,
code_point,
size: Some(iced::Pixels(12.0)),
spacing: 1.0,
side: text_input::Side::Right,
})
.into(),
None => input.into(),
}
}
/// Transparent rail with a white bar handle, for both slider directions.
fn handle_style(_theme: &Theme, _status: slider::Status) -> slider::Style {
slider::Style {
rail: slider::Rail {
backgrounds: (Color::TRANSPARENT.into(), Color::TRANSPARENT.into()),
width: TRACK,
border: Border::default(),
},
handle: slider::Handle {
shape: slider::HandleShape::Rectangle {
width: 8,
border_radius: 3.0.into(),
},
background: Color::WHITE.into(),
border_width: 1.0,
border_color: Color::from_rgba(0.0, 0.0, 0.0, 0.4),
},
}
}
/// A rounded box filled with a linear gradient. Angles run clockwise from
/// "bottom to top": a quarter turn is left to right, a half turn top to bottom.
fn gradient_box(angle: f32, stops: &[(f32, Color)]) -> container::Style {
let linear = stops.iter().fold(
iced::gradient::Linear::new(Radians(angle)),
|linear, &(at, color)| linear.add_stop(at, color),
);
container::Style {
background: Some(Background::Gradient(Gradient::Linear(linear))),
border: Border {
color: Color::from_rgba(0.0, 0.0, 0.0, 0.35),
width: 1.0,
radius: 5.0.into(),
},
..Default::default()
}
}
fn hue_stops() -> Vec<(f32, Color)> {
(0..=6)
.map(|step| {
let at = step as f32 / 6.0;
(at, hsb_color(at * 360.0, 1.0, 1.0))
})
.collect()
}
fn hsb_color(h: f32, s: f32, b: f32) -> Color {
rgb_color(math::hsb_to_rgb(h, s, b))
}
pub(super) fn rgb_color((r, g, b): (u8, u8, u8)) -> Color {
Color::from_rgb8(r, g, b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sliders_produce_colours() {
let mut e = ColorEditor::new((255, 0, 0));
assert!(e.update(EditorMsg::Hue(120.0)));
assert_eq!(e.rgb(), (0, 255, 0));
e.update(EditorMsg::Red(10));
assert_eq!(e.rgb(), (10, 255, 0));
assert!(!e.update(EditorMsg::Mode(EditorMode::Rgb)));
}
#[test]
fn hue_survives_a_trip_through_gray() {
let mut e = ColorEditor::new((0, 0, 255));
e.update(EditorMsg::Saturation(0.0));
e.update(EditorMsg::Saturation(100.0));
assert_eq!(e.rgb(), (0, 0, 255));
}
#[test]
fn hex_applies_six_digits_while_typing() {
let mut e = ColorEditor::new((0, 0, 0));
assert!(!e.update(EditorMsg::HexInput("#39BF".into())));
assert!(e.update(EditorMsg::HexInput("#39BFFC".into())));
assert_eq!(e.rgb(), (57, 191, 252));
}
#[test]
fn short_hex_waits_for_enter() {
let mut e = ColorEditor::new((0, 0, 0));
assert!(!e.update(EditorMsg::HexInput("#3bf".into())));
assert!(e.update(EditorMsg::HexSubmit));
assert_eq!(e.rgb(), (0x33, 0xBB, 0xFF));
}
}
+184
View File
@@ -0,0 +1,184 @@
//! Pick a colour from anywhere inside the application window.
//!
//! While active, a transparent layer covers the window with a crosshair. The
//! first mouse move after the panel is hidden asks iced for a screenshot of
//! the window; a magnifier beside the cursor shows the pixels around it from
//! that screenshot, and a click picks the centre pixel.
use super::editor::rgb_color;
use super::{math, ColorPickerMsg};
use crate::app::Message;
use iced::widget::{column, container, mouse_area, pin, row, stack, text, Space};
use iced::window::Screenshot;
use iced::{Border, Color, Element, Length, Point, Theme};
/// Pixels shown across the magnifier (odd, so one sits in the centre).
const GRID: i32 = 11;
const CELL: f32 = 10.0;
/// Gap between the cursor and the magnifier.
const OFFSET: f32 = 20.0;
#[derive(Debug, Clone)]
pub enum EyedropperMsg {
Start,
Move(Point),
Shot(Screenshot),
Pick,
}
#[derive(Default)]
pub struct Eyedropper {
shot: Option<Screenshot>,
requested: bool,
cursor: Option<Point>,
}
impl Eyedropper {
/// Track the cursor. Returns true when the window screenshot should be
/// requested now — the panel is gone from the screen by then.
pub fn track(&mut self, cursor: Point) -> bool {
self.cursor = Some(cursor);
let request = self.shot.is_none() && !self.requested;
self.requested = true;
request
}
pub fn set_shot(&mut self, shot: Screenshot) {
self.shot = Some(shot);
}
/// The pixel under the cursor, once the screenshot has arrived.
pub fn picked(&self) -> Option<(u8, u8, u8)> {
sample(self.shot.as_ref()?, self.cursor?, 0, 0)
}
pub fn view(&self) -> Element<'_, Message> {
let eye = |m| Message::ColorPicker(ColorPickerMsg::Eyedropper(m));
let catcher = mouse_area(Space::new().width(Length::Fill).height(Length::Fill))
.on_move(move |p| eye(EyedropperMsg::Move(p)))
.on_press(eye(EyedropperMsg::Pick))
.interaction(iced::mouse::Interaction::Crosshair);
let (Some(shot), Some(cursor)) = (self.shot.as_ref(), self.cursor) else {
return catcher.into();
};
let half = GRID / 2;
let mut grid = column![];
for dy in -half..=half {
let mut line = row![];
for dx in -half..=half {
let fill = sample(shot, cursor, dx, dy).map_or(Color::TRANSPARENT, rgb_color);
let centre = dx == 0 && dy == 0;
line = line.push(container(Space::new()).width(CELL).height(CELL).style(
move |_theme: &Theme| container::Style {
background: Some(fill.into()),
border: Border {
color: if centre {
Color::WHITE
} else {
Color::TRANSPARENT
},
width: if centre { 1.5 } else { 0.0 },
radius: 0.0.into(),
},
..Default::default()
},
));
}
grid = grid.push(line);
}
let (r, g, b) = self.picked().unwrap_or((0, 0, 0));
let loupe = container(
column![
grid,
text(math::hex_string(r, g, b))
.size(11)
.font(iced::Font::MONOSPACE),
]
.spacing(4)
.align_x(iced::Center),
)
.padding(4)
.style(|theme: &Theme| {
let palette = theme.palette();
container::Style {
background: Some(palette.background.base.color.into()),
text_color: Some(palette.background.base.text),
border: Border {
color: palette.background.strong.color,
width: 1.0,
radius: 6.0.into(),
},
..Default::default()
}
});
// Keep the magnifier on screen: flip it to the other side of the
// cursor near the right and bottom edges.
let size = GRID as f32 * CELL + 8.0;
let window_w = shot.size.width as f32 / shot.scale_factor;
let window_h = shot.size.height as f32 / shot.scale_factor;
// In a very small window neither side fits; stay inside the top-left.
let x = if cursor.x + OFFSET + size > window_w {
cursor.x - OFFSET - size
} else {
cursor.x + OFFSET
}
.max(0.0);
let y = if cursor.y + OFFSET + size + 20.0 > window_h {
cursor.y - OFFSET - size - 20.0
} else {
cursor.y + OFFSET
}
.max(0.0);
stack![catcher, pin(loupe).x(x).y(y)].into()
}
}
/// The screenshot pixel `dx`, `dy` physical pixels away from a logical point.
fn sample(shot: &Screenshot, at: Point, dx: i32, dy: i32) -> Option<(u8, u8, u8)> {
let x = (at.x * shot.scale_factor) as i64 + i64::from(dx);
let y = (at.y * shot.scale_factor) as i64 + i64::from(dy);
let (w, h) = (i64::from(shot.size.width), i64::from(shot.size.height));
if !(0..w).contains(&x) || !(0..h).contains(&y) {
return None;
}
let i = ((y * w + x) * 4) as usize;
let px = shot.rgba.get(i..i + 3)?;
Some((px[0], px[1], px[2]))
}
#[cfg(test)]
mod tests {
use super::*;
fn shot() -> Screenshot {
// 4×2 physical pixels at scale 2: pixel (x, y) has red = x, green = y.
let mut rgba = Vec::new();
for y in 0..2u8 {
for x in 0..4u8 {
rgba.extend([x, y, 9, 255]);
}
}
Screenshot::new(rgba, iced::Size::new(4, 2), 2.0)
}
#[test]
fn samples_physical_pixels_from_logical_points() {
let s = shot();
assert_eq!(sample(&s, Point::new(1.0, 0.5), 0, 0), Some((2, 1, 9)));
assert_eq!(sample(&s, Point::new(1.0, 0.5), 1, -1), Some((3, 0, 9)));
assert_eq!(sample(&s, Point::new(1.9, 0.0), 1, 0), None);
}
#[test]
fn asks_for_one_screenshot_and_picks_after_it_arrives() {
let mut e = Eyedropper::default();
assert!(e.track(Point::new(0.0, 0.0)));
assert!(!e.track(Point::new(1.0, 0.0)));
assert_eq!(e.picked(), None);
e.set_shot(shot());
assert_eq!(e.picked(), Some((2, 0, 9)));
}
}
+212
View File
@@ -0,0 +1,212 @@
//! Colour maths and formatting for the Select Color panel.
//!
//! Everything here is pure so it can be unit tested without a UI: HSB/RGB
//! conversion, hex formatting and parsing, nearest-index lookup and the
//! short names the panel shows.
use codec::types::Color as AcadColor;
/// Hue in degrees (0–360), saturation and brightness in 0–1.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Hsb {
pub h: f32,
pub s: f32,
pub b: f32,
}
impl Hsb {
pub fn to_rgb(self) -> (u8, u8, u8) {
hsb_to_rgb(self.h, self.s, self.b)
}
}
/// Convert hue (degrees), saturation and brightness (0–1) to 8-bit RGB.
pub fn hsb_to_rgb(h: f32, s: f32, b: f32) -> (u8, u8, u8) {
let h = h.rem_euclid(360.0);
let s = s.clamp(0.0, 1.0);
let b = b.clamp(0.0, 1.0);
let channel = |n: f32| {
let k = (n + h / 60.0) % 6.0;
let v = b - b * s * k.min(4.0 - k).clamp(0.0, 1.0);
(v * 255.0).round() as u8
};
(channel(5.0), channel(3.0), channel(1.0))
}
/// Convert 8-bit RGB to HSB. Grays have no hue; they report 0.
pub fn rgb_to_hsb(r: u8, g: u8, b: u8) -> Hsb {
let (r, g, b) = (
f32::from(r) / 255.0,
f32::from(g) / 255.0,
f32::from(b) / 255.0,
);
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let delta = max - min;
let mut h = if delta == 0.0 {
0.0
} else if max == r {
60.0 * (((g - b) / delta) % 6.0)
} else if max == g {
60.0 * ((b - r) / delta + 2.0)
} else {
60.0 * ((r - g) / delta + 4.0)
};
if h < 0.0 {
h += 360.0;
}
let s = if max == 0.0 { 0.0 } else { delta / max };
Hsb { h, s, b: max }
}
/// Cyan, magenta, yellow and black in percent, by the plain device-free
/// formula (no ICC profile): screen values, not press values.
pub fn rgb_to_cmyk(r: u8, g: u8, b: u8) -> [f32; 4] {
let max = r.max(g).max(b);
if max == 0 {
return [0.0, 0.0, 0.0, 100.0];
}
let max = f32::from(max);
let ink = |v: u8| (1.0 - f32::from(v) / max) * 100.0;
[ink(r), ink(g), ink(b), (1.0 - max / 255.0) * 100.0]
}
pub fn cmyk_to_rgb([c, m, y, k]: [f32; 4]) -> (u8, u8, u8) {
let white = 255.0 * (1.0 - k.clamp(0.0, 100.0) / 100.0);
let channel = |ink: f32| (white * (1.0 - ink.clamp(0.0, 100.0) / 100.0)).round() as u8;
(channel(c), channel(m), channel(y))
}
/// RGB of an ACI entry, or `None` outside 1–255.
pub fn aci_rgb(index: u8) -> Option<(u8, u8, u8)> {
codec::types::aci_table::aci_to_rgb(index)
}
/// The RGB a concrete colour displays as. Logical colours have none.
pub fn color_rgb(color: AcadColor) -> Option<(u8, u8, u8)> {
match color {
AcadColor::Index(i) => aci_rgb(i),
AcadColor::Rgb { r, g, b } => Some((r, g, b)),
_ => None,
}
}
/// The closest index colour to an RGB value.
pub fn nearest_aci(r: u8, g: u8, b: u8) -> u8 {
codec::types::aci_table::nearest_aci(r, g, b)
}
/// `#RRGGBB`, upper case — the same format the Options hex fields store.
pub fn hex_string(r: u8, g: u8, b: u8) -> String {
crate::app::config::rgb_to_hex([r, g, b])
}
/// Parse `#RRGGBB`, `RRGGBB`, `#RGB` or `RGB`. Unlike `config::parse_hex`,
/// which Options uses, this also accepts the short three-digit form.
pub fn parse_hex(text: &str) -> Option<(u8, u8, u8)> {
let digits = text.trim().trim_start_matches('#');
if !digits.chars().all(|c| c.is_ascii_hexdigit()) {
return None;
}
let expanded: String = match digits.len() {
3 => digits.chars().flat_map(|c| [c, c]).collect(),
6 => digits.to_string(),
_ => return None,
};
let value = u32::from_str_radix(&expanded, 16).ok()?;
Some(((value >> 16) as u8, (value >> 8) as u8, value as u8))
}
/// The short name shown on the Current / New swatch.
pub fn short_name(color: AcadColor) -> String {
match color {
AcadColor::Index(i) => format!("ACI {i}"),
AcadColor::Rgb { r, g, b } => hex_string(r, g, b),
AcadColor::ByLayer => "ByLayer".to_string(),
AcadColor::ByBlock => "ByBlock".to_string(),
AcadColor::None => "None".to_string(),
}
}
/// One-line description for the panel's info line.
pub fn describe(color: AcadColor) -> String {
match color {
AcadColor::Index(i) => {
let (r, g, b) = aci_rgb(i).unwrap_or((128, 128, 128));
format!("ACI {i} · RGB {r}, {g}, {b}")
}
AcadColor::Rgb { r, g, b } => format!("RGB {r}, {g}, {b} · {}", hex_string(r, g, b)),
other => short_name(other),
}
}
/// Black or white text, whichever reads better on the given background.
pub fn ink_is_dark(r: u8, g: u8, b: u8) -> bool {
0.299 * f32::from(r) + 0.587 * f32::from(g) + 0.114 * f32::from(b) > 150.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hsb_round_trips_primary_and_mixed_colours() {
for (r, g, b) in [
(255, 0, 0),
(0, 255, 0),
(0, 0, 255),
(57, 191, 252),
(120, 200, 80),
(128, 128, 128),
(0, 0, 0),
(255, 255, 255),
] {
let hsb = rgb_to_hsb(r, g, b);
assert_eq!(hsb.to_rgb(), (r, g, b), "round trip of {r},{g},{b}");
}
}
#[test]
fn hue_of_pure_colours() {
assert_eq!(rgb_to_hsb(255, 0, 0).h, 0.0);
assert!((rgb_to_hsb(0, 255, 0).h - 120.0).abs() < 0.01);
assert!((rgb_to_hsb(0, 0, 255).h - 240.0).abs() < 0.01);
assert_eq!(rgb_to_hsb(80, 80, 80).s, 0.0);
}
#[test]
fn hue_wraps_around() {
assert_eq!(hsb_to_rgb(360.0, 1.0, 1.0), (255, 0, 0));
assert_eq!(hsb_to_rgb(-120.0, 1.0, 1.0), hsb_to_rgb(240.0, 1.0, 1.0));
}
#[test]
fn nearest_aci_comes_from_the_codec_table() {
assert_eq!(nearest_aci(255, 0, 0), 1);
assert_eq!(nearest_aci(250, 5, 5), 1);
}
#[test]
fn short_names() {
assert_eq!(short_name(AcadColor::Index(140)), "ACI 140");
assert_eq!(
short_name(AcadColor::Rgb {
r: 57,
g: 191,
b: 252
}),
"#39BFFC"
);
assert_eq!(short_name(AcadColor::ByLayer), "ByLayer");
}
#[test]
fn hex_formats_and_parses() {
assert_eq!(hex_string(57, 191, 252), "#39BFFC");
assert_eq!(parse_hex("#39bffc"), Some((57, 191, 252)));
assert_eq!(parse_hex("39BFFC"), Some((57, 191, 252)));
assert_eq!(parse_hex("#3bf"), Some((0x33, 0xBB, 0xFF)));
assert_eq!(parse_hex("#39BFF"), None);
assert_eq!(parse_hex("zzzzzz"), None);
}
}
@@ -3,6 +3,17 @@
//! properties panel and every style editor so colour selection looks and
//! behaves the same everywhere.
pub mod book;
pub mod document;
pub mod editor;
pub mod eyedropper;
pub mod math;
pub mod picker;
pub mod widgets;
pub use math::nearest_aci;
pub use picker::{ColorPicker, ColorPickerMsg, Outcome};
use crate::app::Message;
use crate::t;
use crate::ui::properties::acad_color_display;
@@ -34,42 +45,6 @@ pub fn color_to_aci_string(c: AcadColor) -> String {
}
}
/// Convert an Iced colour chosen by `iced_aw::ColorPicker` into a DWG true
/// colour. ACI-only destinations map it to their closest indexed colour later.
pub fn iced_to_acad_color(color: Color) -> AcadColor {
// Truncate the way the picker labels its channels, so the stored colour
// is the one the dialog showed; rounding drifted dragged values by one
// (#1621).
let byte = |c: f32| (c * 255.0) as u8;
AcadColor::Rgb {
r: byte(color.r),
g: byte(color.g),
b: byte(color.b),
}
}
/// Return the closest CAD Color Index for an RGB colour.
pub fn nearest_aci(r: u8, g: u8, b: u8) -> u8 {
let mut best = 7;
let mut best_distance = u32::MAX;
for index in 1..=255 {
let Some((ar, ag, ab)) = codec::types::aci_table::aci_to_rgb(index) else {
continue;
};
let dr = i32::from(r) - i32::from(ar);
let dg = i32::from(g) - i32::from(ag);
let db = i32::from(b) - i32::from(ab);
let distance = (dr * dr + dg * dg + db * db) as u32;
if distance < best_distance {
best = index;
best_distance = distance;
}
}
best
}
/// Decode an ACI integer string back into an `AcadColor`.
pub fn aci_string_to_color(s: &str) -> AcadColor {
match s.trim().parse::<i16>().unwrap_or(256) {
@@ -349,174 +324,3 @@ pub fn drop_down_below<'a>(
}
dd.into()
}
/// Full CAD indexed-colour page used by the standalone Select Color dialog.
///
/// This only edits the pending colour. The caller commits it with
/// `Message::ColorWindowPick`, so choosing a swatch does not immediately close
/// the dialog.
pub fn index_color_page<'a>(
current: AcadColor,
recent_colors: &'a [AcadColor],
) -> Element<'a, Message> {
let swatch_button = |color: AcadColor, size: f32| -> Element<'a, Message> {
let (bg, _) = acad_color_display(color);
let selected = current == color;
button(
text("")
.width(Length::Fixed(size))
.height(Length::Fixed(size)),
)
.on_press(Message::ColorPickerColorChanged(color))
.style(move |theme: &Theme, status| {
let hovered = matches!(status, button::Status::Hovered | button::Status::Pressed);
button::Style {
background: Some(Background::Color(bg)),
border: Border {
color: if selected || hovered {
theme.palette().primary.base.color
} else {
theme.palette().background.neutral.color
},
width: if selected {
2.5
} else if hovered {
1.5
} else {
1.0
},
radius: 1.0.into(),
},
..Default::default()
}
})
.padding(0)
.into()
};
// The first standard indexed CAD colours.
let mut standard = row![].spacing(4);
for idx in 1u8..=9 {
standard = standard.push(swatch_button(AcadColor::Index(idx), 22.0));
}
// ACI 10-249.
//
// Each group of ten indices belongs together. Arrange the 24 colour
// families horizontally and their ten shade/intensity variants vertically.
// This makes the palette read as a colour spectrum instead of a raw numeric
// sequence.
let mut palette_rows = column![].spacing(2);
for variant in 0u8..10 {
let mut palette_row = row![].spacing(2);
for family in 1u8..=24 {
let idx = family * 10 + variant;
palette_row = palette_row.push(swatch_button(AcadColor::Index(idx), 16.0));
}
palette_rows = palette_rows.push(palette_row);
}
// ACI 250-255 are the grayscale entries and are shown separately.
let mut grayscale = row![].spacing(4);
for idx in 250u8..=255 {
grayscale = grayscale.push(swatch_button(AcadColor::Index(idx), 22.0));
}
let recent: Element<'a, Message> = if recent_colors.is_empty() {
text(crate::t!("No recent colors yet"))
.size(10)
.style(|theme: &Theme| iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.55)),
})
.into()
} else {
let mut recent_row = row![].spacing(4);
for &color in recent_colors.iter().take(12) {
recent_row = recent_row.push(swatch_button(color, 22.0));
}
recent_row.into()
};
let selected_text = match current {
AcadColor::Index(i) => {
let (r, g, b) = codec::types::aci_table::aci_to_rgb(i).unwrap_or((128, 128, 128));
format!("ACI {i} RGB {r}, {g}, {b}")
}
AcadColor::Rgb { r, g, b } => crate::tf!("True Color RGB {r}, {g}, {b}").into_owned(),
AcadColor::ByLayer => t!("ByLayer").into_owned(),
AcadColor::ByBlock => t!("ByBlock").into_owned(),
AcadColor::None => t!("None").into_owned(),
};
let tabs = row![
button(text(crate::t!("Index Color")).size(12))
.style(button::primary)
.padding([5, 14]),
button(text(crate::t!("True Color")).size(12))
.on_press(Message::ColorPickerTabChanged(
crate::app::ColorPickerTab::TrueColor
))
.style(button::secondary)
.padding([5, 14]),
]
.spacing(4);
let actions = container(
row![
button(text(crate::t!("Cancel")).size(11))
.on_press(Message::CloseColorPicker)
.style(button::secondary)
.padding([5, 14]),
button(text(crate::t!("OK")).size(11))
.on_press(Message::ColorWindowPick(current))
.style(button::primary)
.padding([5, 18]),
]
.spacing(8),
)
.width(Length::Fill)
.align_x(iced::alignment::Horizontal::Right);
container(
column![
tabs,
text(crate::t!("Standard colors")).size(11),
standard,
text(crate::t!("CAD Color Index (ACI) 10–249")).size(11),
text(crate::t!("Color family → · Shade / intensity ↓"))
.size(9)
.style(|theme: &Theme| iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.65)),
}),
container(palette_rows)
.padding(6)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(theme.palette().background.weakest.color)),
border: Border {
color: theme.palette().background.neutral.color,
width: 1.0,
radius: 3.0.into(),
},
..Default::default()
}),
text(crate::t!("Grayscale 250–255")).size(11),
grayscale,
text(crate::t!("Recently used")).size(11),
recent,
text(selected_text).size(11),
actions,
]
.spacing(9),
)
.padding(10)
.width(Length::Fixed(470.0))
.into()
}
File diff suppressed because it is too large. Load diff
+258
View File
@@ -0,0 +1,258 @@
//! Saturation/brightness field for one hue.
//!
//! Drawn as three layered quads instead of pixel by pixel: the solid hue, a
//! white-to-transparent wash from left to right, and a transparent-to-black
//! wash from top to bottom. Saturation runs left to right, brightness bottom
//! to top. Dragging anywhere moves the marker.
use iced::advanced::layout::{self, Layout};
use iced::advanced::widget::{self, tree, Widget};
use iced::advanced::{mouse, renderer, Shell};
use iced::gradient::Linear;
use iced::{
Background, Border, Color, Element, Event, Gradient, Length, Point, Radians, Rectangle,
Renderer, Size, Theme,
};
const RADIUS: f32 = 5.0;
const MARKER: f32 = 16.0;
pub struct ColorArea<'a, Message> {
/// Fully saturated, full-brightness colour of the current hue.
hue_color: Color,
/// The colour under the marker.
marker_color: Color,
saturation: f32,
brightness: f32,
on_change: Box<dyn Fn(f32, f32) -> Message + 'a>,
width: Length,
height: Length,
}
#[derive(Default)]
struct State {
dragging: bool,
}
impl<'a, Message> ColorArea<'a, Message> {
/// `saturation` and `brightness` are 0–1; `on_change` receives the same.
pub fn new(
hue_color: Color,
marker_color: Color,
saturation: f32,
brightness: f32,
on_change: impl Fn(f32, f32) -> Message + 'a,
) -> Self {
Self {
hue_color,
marker_color,
saturation: saturation.clamp(0.0, 1.0),
brightness: brightness.clamp(0.0, 1.0),
on_change: Box::new(on_change),
width: Length::Fill,
height: Length::Fixed(150.0),
}
}
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
fn values_at(bounds: Rectangle, cursor: Point) -> (f32, f32) {
let s = ((cursor.x - bounds.x) / bounds.width.max(1.0)).clamp(0.0, 1.0);
let b = 1.0 - ((cursor.y - bounds.y) / bounds.height.max(1.0)).clamp(0.0, 1.0);
(s, b)
}
}
impl<Message> Widget<Message, Theme, Renderer> for ColorArea<'_, Message> {
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
tree::State::new(State::default())
}
fn size(&self) -> Size<Length> {
Size::new(self.width, self.height)
}
fn layout(
&mut self,
_tree: &mut widget::Tree,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout::atomic(limits, self.width, self.height)
}
fn update(
&mut self,
tree: &mut widget::Tree,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
_renderer: &Renderer,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) {
let state = tree.state.downcast_mut::<State>();
let bounds = layout.bounds();
let publish = |shell: &mut Shell<'_, Message>, position: Point| {
let (s, b) = Self::values_at(bounds, position);
shell.publish((self.on_change)(s, b));
shell.capture_event();
};
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
if let Some(position) = cursor.position_over(bounds) {
state.dragging = true;
publish(shell, position);
}
}
Event::Mouse(mouse::Event::CursorMoved { .. }) if state.dragging => {
if let Some(position) = cursor.position() {
publish(shell, position);
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) if state.dragging => {
state.dragging = false;
shell.capture_event();
}
_ => {}
}
}
fn mouse_interaction(
&self,
tree: &widget::Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
if tree.state.downcast_ref::<State>().dragging || cursor.is_over(layout.bounds()) {
mouse::Interaction::Crosshair
} else {
mouse::Interaction::default()
}
}
fn draw(
&self,
_tree: &widget::Tree,
renderer: &mut Renderer,
_theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
use iced::advanced::Renderer as _;
let bounds = layout.bounds();
let rounded = renderer::Quad {
bounds,
border: Border {
radius: RADIUS.into(),
..Border::default()
},
..renderer::Quad::default()
};
let wash = |angle: f32, from: Color, to: Color| {
Background::Gradient(Gradient::Linear(
Linear::new(Radians(angle))
.add_stop(0.0, from)
.add_stop(1.0, to),
))
};
renderer.fill_quad(rounded, self.hue_color);
renderer.fill_quad(
rounded,
wash(
std::f32::consts::FRAC_PI_2,
Color::WHITE,
Color {
a: 0.0,
..Color::WHITE
},
),
);
renderer.fill_quad(
renderer::Quad {
border: Border {
color: Color::from_rgba(0.0, 0.0, 0.0, 0.5),
width: 1.0,
radius: RADIUS.into(),
},
..rounded
},
wash(
std::f32::consts::PI,
Color {
a: 0.0,
..Color::BLACK
},
Color::BLACK,
),
);
let centre = Point::new(
bounds.x + bounds.width * self.saturation,
bounds.y + bounds.height * (1.0 - self.brightness),
);
let marker = |size: f32| Rectangle {
x: centre.x - size / 2.0,
y: centre.y - size / 2.0,
width: size,
height: size,
};
renderer.fill_quad(
renderer::Quad {
bounds: marker(MARKER + 2.0),
border: Border {
radius: ((MARKER + 2.0) / 2.0).into(),
..Border::default()
},
..renderer::Quad::default()
},
Color::from_rgba(0.0, 0.0, 0.0, 0.55),
);
renderer.fill_quad(
renderer::Quad {
bounds: marker(MARKER),
border: Border {
color: Color::WHITE,
width: 2.0,
radius: (MARKER / 2.0).into(),
},
..renderer::Quad::default()
},
self.marker_color,
);
}
}
impl<'a, Message: 'a> From<ColorArea<'a, Message>> for Element<'a, Message> {
fn from(area: ColorArea<'a, Message>) -> Self {
Element::new(area)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn corners_map_to_saturation_and_brightness() {
let bounds = Rectangle::new(Point::new(10.0, 20.0), Size::new(200.0, 100.0));
let at = |x, y| ColorArea::<()>::values_at(bounds, Point::new(x, y));
assert_eq!(at(10.0, 20.0), (0.0, 1.0));
assert_eq!(at(210.0, 120.0), (1.0, 0.0));
assert_eq!(at(110.0, 70.0), (0.5, 0.5));
assert_eq!(at(-5.0, 500.0), (0.0, 0.0));
}
}
+5
View File
@@ -0,0 +1,5 @@
//! Widgets the Select Color panel needs that iced does not provide.
mod color_area;
pub use color_area::ColorArea;
+3
View File
@@ -62,6 +62,7 @@ static LAY_ULK: &[u8] = include_bytes!("../../assets/icons/layers/layulk.svg");
// All are black-on-transparent; recolour them at the call site with [`tinted`].
pub static CHECK: &[u8] = include_bytes!("../../assets/icons/ui/check.svg");
pub static CLOSE: &[u8] = include_bytes!("../../assets/icons/ui/close.svg");
pub static EYEDROPPER: &[u8] = include_bytes!("../../assets/icons/ui/eyedropper.svg");
pub static PLUS: &[u8] = include_bytes!("../../assets/icons/ui/plus.svg");
pub static REFRESH: &[u8] = include_bytes!("../../assets/icons/ui/refresh.svg");
pub static SWAP: &[u8] = include_bytes!("../../assets/icons/ui/swap.svg");
@@ -80,6 +81,8 @@ pub static PIN_ACTIVE: &[u8] = include_bytes!("../../assets/icons/ui/pin_active.
pub static SPLIT_V: &[u8] = include_bytes!("../../assets/icons/ui/split_v.svg");
pub static SPLIT_H: &[u8] = include_bytes!("../../assets/icons/ui/split_h.svg");
pub static GRID: &[u8] = include_bytes!("../../assets/icons/ui/grid.svg");
pub static LIST: &[u8] = include_bytes!("../../assets/icons/ui/list.svg");
pub static SEARCH: &[u8] = include_bytes!("../../assets/icons/ui/search.svg");
pub static PREVIEW: &[u8] = include_bytes!("../../assets/icons/ui/preview.svg");
pub static SNAP: &[u8] = include_bytes!("../../assets/icons/ui/snap.svg");
pub static DOC_NEW: &[u8] = include_bytes!("../../assets/icons/ui/doc_new.svg");
+7 -3
View File
@@ -157,12 +157,14 @@ fn divider<'a>() -> Element<'a, Message> {
.into()
}
/// `shown` overrides the swatch, so ByLayer can show the layer's colour.
fn logical_row<'a>(
color: AcadColor,
cur_color: AcadColor,
label: &'static str,
shown: Option<iced::Color>,
) -> Element<'a, Message> {
let (bg, _) = acad_color_display(color);
let bg = shown.unwrap_or_else(|| acad_color_display(color).0);
let selected = color == cur_color;
let swatch = container(
@@ -212,13 +214,15 @@ fn more_colors_row<'a>(active_color: AcadColor) -> Element<'a, Message> {
}
/// Builds the complete 5-section color dropdown panel element.
/// `layer_color` is the current layer's colour, shown on the ByLayer row.
pub fn color_dropdown_panel<'a>(
active_color: AcadColor,
recent_colors: &[AcadColor],
layer_color: Option<iced::Color>,
) -> Element<'a, Message> {
// 1. ByLayer and ByBlock
let by_layer = logical_row(AcadColor::ByLayer, active_color, "ByLayer");
let by_block = logical_row(AcadColor::ByBlock, active_color, "ByBlock");
let by_layer = logical_row(AcadColor::ByLayer, active_color, "ByLayer", layer_color);
let by_block = logical_row(AcadColor::ByBlock, active_color, "ByBlock", None);
let section1 = column![by_layer, by_block].spacing(2);
// 2. 45-swatch Quick Pick Grid (5 rows × 9 columns)
+31 -1
View File
@@ -88,6 +88,9 @@ pub struct Ribbon {
pub layer_filter: String,
/// Active object color — ACI / ByLayer / ByBlock.
pub active_color: AcadColor,
/// Colour-book identity (`BOOK$NAME`) of a colour picked from a colour
/// book, with the colour it was picked as; see `active_color_name`.
pub active_color_book: Option<(AcadColor, String)>,
/// Active linetype override ("ByLayer", "Continuous", …).
pub active_linetype: String,
/// Active lineweight.
@@ -145,6 +148,14 @@ pub struct LayerInfo {
pub vp_frozen: Option<(usize, bool)>,
}
/// The colour of layer `name` in the ribbon's layer list.
pub(super) fn layer_color(layers: &[LayerInfo], name: &str) -> Option<Color> {
layers
.iter()
.find(|layer| layer.name == name)
.map(|layer| layer.color)
}
/// Full-screen backdrop for an open ribbon dropdown: closes the dropdown when
/// the user clicks outside the panel. (Cursor motion leaking to the viewport
/// beneath is blocked in `on_viewport_move`, not here — iced 0.14's mouse_area
@@ -183,6 +194,20 @@ fn position_ribbon_dropdown<'a>(
}
impl Ribbon {
/// The current layer's colour: what ByLayer looks like for new objects.
pub fn active_layer_color(&self) -> Option<Color> {
layer_color(&self.layer_infos, &self.active_layer)
}
/// The colour-book identity of the active colour, while the active colour
/// is still the one picked from the book.
pub fn active_color_name(&self) -> Option<String> {
self.active_color_book
.as_ref()
.filter(|(color, _)| *color == self.active_color)
.map(|(_, name)| name.clone())
}
pub fn new() -> Self {
Self {
modules: all_ribbon_modules(),
@@ -207,6 +232,7 @@ impl Ribbon {
layer_infos: vec![],
layer_filter: String::new(),
active_color: AcadColor::ByLayer,
active_color_book: None,
active_linetype: "ByLayer".to_string(),
active_lineweight: LineWeight::ByLayer,
available_linetypes: vec![LinetypeItem {
@@ -1293,7 +1319,11 @@ impl Ribbon {
win: (f32, f32),
recent_colors: &[AcadColor],
) -> Option<Element<'_, Message>> {
let panel = color_dropdown::color_dropdown_panel(self.active_color, recent_colors);
let panel = color_dropdown::color_dropdown_panel(
self.active_color,
recent_colors,
self.active_layer_color(),
);
let (align_right, h_pad, top) =
self.dd_anchor(PROP_COLOR_ID, color_dropdown::PANEL_W, win.0);
+4 -1
View File
@@ -1193,7 +1193,10 @@ pub(super) fn render_large<'a>(item: &RibbonItem, ctx: &RenderCtx<'_>) -> Elemen
.width(Length::Fixed(PROP_W))
};
let (color_swatch, _) = acad_color_display(active_color);
// ByLayer shows the current layer's colour.
let color_swatch = super::layer_color(layer_infos, active_layer)
.filter(|_| active_color == AcadColor::ByLayer)
.unwrap_or_else(|| acad_color_display(active_color).0);
let color_row = prop_row(
crate::ui::color_select::color_display_name(active_color),
PROP_COLOR_ID,
+36 -92
View File
@@ -108,6 +108,8 @@ pub struct Folders {
/// Where `.shx` / `.ttf` fonts dropped in by the user (or downloaded for
/// a drawing) are found.
pub fonts: Option<String>,
/// Where `.acb` colour books are kept and loaded ones copied to.
pub color_books: Option<String>,
}
/// Values read from the current drawing's header rather than from preferences.
@@ -165,22 +167,17 @@ impl<T> fmt::Display for Labelled<T> {
}
}
/// A background swatch that opens the colour wheel when clicked.
/// A colour swatch that opens the Select Color panel when clicked.
///
/// The wheel sits alongside the hex field rather than replacing it: typing
/// The swatch sits beside the hex field rather than replacing it: typing
/// `#1E1E1E` stays the fastest way to reproduce an exact colour, while the
/// wheel is for choosing one by eye.
fn bg_swatch_picker<'a>(
target: crate::app::BgTarget,
rgb: [u8; 3],
open: Option<crate::app::BgTarget>,
) -> Element<'a, Message> {
let colour = iced::Color::from_rgb8(rgb[0], rgb[1], rgb[2]);
/// panel is for choosing one by eye.
fn color_swatch<'a>(target: crate::app::UiColor, [r, g, b]: [u8; 3]) -> Element<'a, Message> {
let swatch = container(Space::new())
.width(28)
.height(22)
.style(move |theme: &Theme| container::Style {
background: Some(Background::Color(colour)),
background: Some(Background::Color(iced::Color::from_rgb8(r, g, b))),
border: Border {
color: theme.palette().background.strong.color,
width: 1.0,
@@ -188,18 +185,14 @@ fn bg_swatch_picker<'a>(
},
..Default::default()
});
let underlay = button(swatch)
.on_press(Message::BgPickerOpen(target))
button(swatch)
.on_press(Message::OpenColorWindow(
crate::app::ColorPickTarget::Interface(target),
codec::types::Color::Rgb { r, g, b },
))
.padding(0)
.style(button::text);
iced_aw::ColorPicker::new(
open == Some(target),
colour,
underlay,
Message::BgPickerCancel,
Message::BgPickerSubmit,
)
.into()
.style(button::text)
.into()
}
#[allow(clippy::too_many_arguments)]
@@ -235,7 +228,6 @@ pub fn view_window<'a>(
model_bg_input: &'a str,
paper_bg_input: &'a str,
desk_bg_input: &'a str,
bg_picker: Option<crate::app::BgTarget>,
dirty: bool,
close_confirm: bool,
sizing: crate::ui::modal::ModalSizing,
@@ -315,18 +307,8 @@ pub fn view_window<'a>(
let mut color_controls = column![].spacing(8);
for (index, (label, color)) in colors.into_iter().enumerate() {
let swatch = container(Space::new())
.width(28)
.height(22)
.style(move |theme: &Theme| container::Style {
background: Some(Background::Color(color)),
border: Border {
color: theme.palette().background.strong.color,
width: 1.0,
radius: 3.0.into(),
},
..Default::default()
});
let [r, g, bl, _] = color.into_rgba8();
let swatch = color_swatch(crate::app::UiColor::Theme(index), [r, g, bl]);
color_controls = color_controls.push(
row![
text(label).size(12).width(110),
@@ -536,61 +518,18 @@ pub fn view_window<'a>(
.spacing(0)
.width(sizing.width);
let crosshair_rgb = crosshair_color.unwrap_or([255, 255, 255]);
let crosshair_swatch =
container(Space::new())
.width(28)
.height(22)
.style(move |theme: &Theme| container::Style {
background: Some(Background::Color(iced::Color::from_rgb8(
crosshair_rgb[0],
crosshair_rgb[1],
crosshair_rgb[2],
))),
border: Border {
color: theme.palette().background.strong.color,
width: 1.0,
radius: 3.0.into(),
},
..Default::default()
});
let command_rgb = command_text_color.unwrap_or([220, 220, 220]);
let command_swatch =
container(Space::new())
.width(28)
.height(22)
.style(move |theme: &Theme| container::Style {
background: Some(Background::Color(iced::Color::from_rgb8(
command_rgb[0],
command_rgb[1],
command_rgb[2],
))),
border: Border {
color: theme.palette().background.strong.color,
width: 1.0,
radius: 3.0.into(),
},
..Default::default()
});
let snap_rgb = snap_marker_color.unwrap_or([255, 230, 26]);
let snap_swatch = container(Space::new())
.width(28)
.height(22)
.style(move |theme: &Theme| container::Style {
background: Some(Background::Color(iced::Color::from_rgb8(
snap_rgb[0],
snap_rgb[1],
snap_rgb[2],
))),
border: Border {
color: theme.palette().background.strong.color,
width: 1.0,
radius: 3.0.into(),
},
..Default::default()
});
let crosshair_swatch = color_swatch(
crate::app::UiColor::Crosshair,
crosshair_color.unwrap_or([255, 255, 255]),
);
let command_swatch = color_swatch(
crate::app::UiColor::CommandText,
command_text_color.unwrap_or([220, 220, 220]),
);
let snap_swatch = color_swatch(
crate::app::UiColor::SnapMarker,
snap_marker_color.unwrap_or([255, 230, 26]),
);
let model_bg_rgb = model_space
.custom_bg
@@ -750,7 +689,7 @@ pub fn view_window<'a>(
display = display.push(Space::new().height(10)).push(
row![
text(crate::t!("Model background")).size(12).width(140),
bg_swatch_picker(crate::app::BgTarget::Model, model_bg_rgb, bg_picker),
color_swatch(crate::app::UiColor::ModelBackground, model_bg_rgb),
text_input("#RRGGBB", model_bg_input)
.on_input(Message::ModelSpaceBgChanged)
.width(150),
@@ -763,7 +702,7 @@ pub fn view_window<'a>(
display = display.push(Space::new().height(10)).push(
row![
text(crate::t!("Paper background")).size(12).width(140),
bg_swatch_picker(crate::app::BgTarget::Paper, paper_bg_rgb, bg_picker),
color_swatch(crate::app::UiColor::PaperBackground, paper_bg_rgb),
text_input("#RRGGBB", paper_bg_input)
.on_input(Message::PaperSpaceBgChanged)
.width(150),
@@ -775,7 +714,7 @@ pub fn view_window<'a>(
display = display.push(Space::new().height(10)).push(
row![
text(crate::t!("Desk surround")).size(12).width(140),
bg_swatch_picker(crate::app::BgTarget::Desk, desk_bg_rgb, bg_picker),
color_swatch(crate::app::UiColor::DeskBackground, desk_bg_rgb),
text_input("#RRGGBB", desk_bg_input)
.on_input(Message::DeskSpaceBgChanged)
.width(150),
@@ -1853,6 +1792,11 @@ pub fn view_window<'a>(
folder_row(crate::t!("Autosave files"), folders.autosave.clone()),
Space::new().height(12),
folder_row(crate::t!("Fonts"), folders.fonts.clone()),
Space::new().height(12),
folder_row(
crate::t!("Color book locations"),
folders.color_books.clone()
),
Space::new().height(16),
text(crate::t!("Default template for new drawings")).size(12),
Space::new().height(4),